-
Notifications
You must be signed in to change notification settings - Fork 42
REST API code generation for Go
From a testability perspective, there’s a somewhat radical idea I have around fully encapsulating the business logic from both its dependants and its dependencies. Consider the following Sysl design:
Backend1:
GET /txs/{crn<:string}:
return <: set of Tx
Backend2:
FetchTrans(custid<:string):
return <: set of Tx
TransactionService:
GET /transactions/{crn<:string}:
Backend1 <- GET /tx
Backend2 <- FetchTrans
return <: set of Transaction
The generated interfaces for Backend[12] and TransactionService would be fairly straightforward, something like:
type Backend1 interface {
GET_txs(ctx context.Context, crn string) ([]Tx, error)
}
type Backend2 interface {
FetchTrans(ctx context.Context, custid string) ([]Tx, error)
}
type TransactionService interface {
GET_transactions(ctx context.Context, crn string) ([]Transaction, error)
}
The TransactionService, however, would have an additional interface, the purpose of which is to define the protocol between the front-end service that implements the endpoint and the implementation of the endpoint:
type TransactionServiceProtocol interface {
GET_transactions(
ctx context.Context,
crn string,
Backend1_GET_tx func(ctx context.Context, crn string) ([]Tx, error),
Backend2_FetchTrans func(ctx context.Context, custid string) ([]Tx, error),
) ([]Transaction, error)
}
The code-generated front-end TransactionService logic will instantiate code-generated Backend[12] clients and pass their bounds methods to the delegated TransactionService interface. Here’s an oversimplified hypothetical generated API frontend:
func HandleTransactionService(
r chi.Router,
ts TransactionServiceProtocol,
backend1 Backend1,
backend2 Backend2,
) {
r.Get("/transactions/{crn}", func(w http.ResponseWriter, r *http.Request) {
crn := chi.URLParam(r, "crn")
err, result := ts.GET_transactions(
r.Context(),
crn,
backend1.GET_txs,
backend2.FetchTrans,
)
if err != nil {
http.Error(w, http.StatusText(500) + ": " + err.Error(), 500)
} else {
json.NewEncoder(w).Encode(result)
}
})
}