Planning out Saga 'Object' | 220525


_2321

I had some code written for a saga, Its not perfect, but I would like to start to mess with it.

 package saga  
      
    import (  
       "fmt"  
       "io"
       "net/http"
       "strings")  
      
    type Saga struct {  
       SageName     string  
      Transactions []*transaction  
    }  
      
    type transaction struct {  
       TransactionName string  
      ServiceName     string  
      TransactionAction  
    }  
      
    func (t *transaction) GetTransaction() *transaction {  
       return t  
    }  
      
    //TODO find a better name for this. "TransactionAction"  
    // I have no idea wtf to call this. I have spent four+ hours looking for what to call this.  
    // It's the action part of the transaction. I am floored on what to call this.  
    // If anyone ever read this please let me.  
    // Cause a transaction has data and it's a function at the same time, technically its a noun but idk  
      
    //TransactionAction generic for the action part of the transactiontype TransactionAction interface {  
       Execute() error  
    }  
      
    //These Definitions are straight form the book  
    //Microservices Patterns By Chris Richardson  
      
    //RetryableTransaction Transactions that follow the pivot transaction and are guaranteed to succeed.type RetryableTransaction struct {  
       transaction  
    }  
      
    //NewRetriableTransaction Constructor for RetryableTransaction  
    func NewRetriableTransaction(transactionName string, serviceName string, transactionAction TransactionAction) *RetryableTransaction {  
       return &RetryableTransaction{transaction{  
          TransactionName:   transactionName,  
      ServiceName:       serviceName,  
      TransactionAction: transactionAction,  
      }}  
    }  
      
    //CompensatableTransaction transaction that can potentially be rolled back using a compensating transaction.type CompensatableTransaction struct {  
       transaction  
      //The is the action that un does this transaction if it fails  
      CompensatableAction TransactionAction  
    }  
      
    //NewCompensatableTransaction Constructor for CompensatableTransaction  
    func NewCompensatableTransaction(transactionName string, serviceName string, transactionAction TransactionAction, compensatableAction TransactionAction) *CompensatableTransaction {  
       return &CompensatableTransaction{  
          transaction: transaction{  
             TransactionName:   transactionName,  
      ServiceName:       serviceName,  
      TransactionAction: transactionAction,  
      },  
      CompensatableAction: compensatableAction,  
      }  
    }  
      
    //PivotTransaction The go/no-go point in a saga. If the pivot transaction commits, the saga will run until completion. A pivot transaction can be a transaction that’s neither compensatable nor retriable. Alternatively, it can be the last compensatable transaction or the first retriable transaction.type PivotTransaction struct {  
       transaction  
    }  
      
    //NewPivotTransaction Constructor for NewPivotTransaction  
    func NewPivotTransaction(transactionName string, serviceName string, transactionAction TransactionAction) *RetryableTransaction {  
       return &RetryableTransaction{transaction{  
          TransactionName:   transactionName,  
      ServiceName:       serviceName,  
      TransactionAction: transactionAction,  
      }}  
    }  
      
    // OKay I am looking back after 2 months of writing most of this, and I am not positive how the theis RestProxy connects  
    // to the code above  
      
    type TransactionRESTProxy struct {  
       TransactionAction  
      HttpFunction string  
      URL          string  
      Body         *io.Reader  
      Response     *http.Response  
    }  
      
    // NewTransactionActionRestProxy is a constructor for the transaction REST Proxy creation just to make sure values make sense before making that call to the other services, or it own servicefunc NewTransactionActionRestProxy(httpFunction string, url string, body *io.Reader) (*TransactionRESTProxy, error) {  
       var ErrMismatchHttpFunctionWithBody = fmt.Errorf("[ERROR] [SAGA] [TRANSACTION] [REST PROXY] [CREATION] | Cannot have body with this %+v | GET or DELETE", httpFunction)  
       var ErrMissingBodyForAssociatedHttpFunction = fmt.Errorf("[ERROR] [SAGA] [TRANSACTION] [REST PROXY] [CREATION] | Cannot have missingbody for this fucntion %+v | POST or UPDATE", httpFunction)  
       var ErrNonSupportedHTTPFunction = fmt.Errorf("[ERROR] [SAGA] [TRANSACTION] [REST PROXY] [CREATION] | %+v is not a supported http Fuction: Supported Functions: POST, GET, UPDATE, and DELETE", httpFunction)  
       groomedHTTPFunction := strings.ToUpper(strings.TrimSpace(httpFunction))  
       switch groomedHTTPFunction {  
       case "GET":  
          {  
             if body != nil {  
                return nil, ErrMismatchHttpFunctionWithBody  
             }  
      
          }  
       case "DELETE":  
          {  
             if body != nil {  
                return nil, ErrMismatchHttpFunctionWithBody  
             }  
          }  
       case "UPDATE":  
          {  
             if body == nil {  
                return nil, ErrMissingBodyForAssociatedHttpFunction  
             }  
          }  
       case "POST":  
          {  
             if body == nil {  
                return nil, ErrMissingBodyForAssociatedHttpFunction  
             }  
          }  
       default:  
          {  
             return nil, ErrNonSupportedHTTPFunction  
          }  
       }  
       //TODO Figure out if should do any check on the url  
      //I am guessing I should check if it in the correct domain like internal, but later  
     //That means I need a file where I keep the domain at for later // // I could also have it be dynamic like a list that updated when services in the "backend" so have script // run at the start of each service // I have a lot idea about this, but none seem to pressing to getting this operational  
      return &TransactionRESTProxy{HttpFunction: groomedHTTPFunction, URL: url, Body: body, Response: nil}, nil  
    }  
      
    func (trp *TransactionRESTProxy) Execute() error {  
      
       client := &http.Client{}  
       req, err := http.NewRequest(trp.HttpFunction, trp.URL, *trp.Body)  
       if err != nil {  
          fmt.Print(err.Error())  
       }  
       req.Header.Add("Accept", "application/json")  
       req.Header.Add("Content-Type", "application/json")  
       resp, err := client.Do(req)  
       defer func(Body io.ReadCloser) {  
          err := Body.Close()  
          if err != nil {  
             // Need to find a way to make this return an error.  
     //  }  
       }(resp.Body)  
       trp.Response = resp  
       return nil  
      
    }

I am not positive that any of this will be useful, but I need to figure out how I am going to keep track of each of the sagas. Cause there doesn’t seem to much in go for sagas that is written

Microsoft and Sagas Microservices Sagas

There appears to be two different ways to go about implementing sagas

Spring (Java) Example of Choreography

So this is the event message system and why was looking at Apache Kafka. I think this more used one or at least form what I can tell the other option being

Spring (Java) Example Orchestration

So this one has the start of the order be created at the start of a saga life and orchestrate it through its life.


So between these two I can see some things the Orchestration would seem to allow me to use the code I have ready written with the saga code above and the status i have in the models for each of services, but that’s not really a good reason. The Choreography seems to more scaleable cause it whole new container to deal with sagas events that scaled to the needs of the system. This does working with another system cause creating my own event message management system seems a bit much for my given scope, even tho external dependencies is not preferable.

I am not positive which one, but I think i’ll try the Kafka one first and see what I get.

Not the most eventful update, but I am getting back into it. Feels good and lil overwhelming cause I have forgot a lot.