Skip to content

REST API code generation for Go

Marcelo Cantos edited this page Jan 8, 2019 · 20 revisions

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}:
        loop:
            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)
        }
    })
}
Clone this wiki locally