OpenAPITools / OpenAPITools/openapi-generator

[REQ] [go-server] Services knowing about HTTP is questionable

Open
#9,647 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Enhancement: Feature
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

Is your feature request related to a problem? Please describe.

Let's look at the service method signatures:

func (s *UserApiService) CreateUser(ctx context.Context, user User) (ImplResponse, error) {
}

The method returns a ImplResponse, carrying a HTTP status code. I see two problems with this.

The first one .... really, really minor: why does a method return an implementation object? It should return an interface, and ImplResponse would be an implementation of that interface.

Now to the other, bigger one.

Imo there's an architectural problem with the idea of controllers and services.

What is the idea of controllers and services?

The idea of the separation between controllers and services is (imo, pls enlighten me otherwise):

  • controllers hold all the connection logic, specific to http in this case.
  • services hold the business logic solely. They don't know anything about HTTP status codes. They return model classes and errors
What's the current state?

Services return response objects, carrying HTTP status codes. Making services HTTP-dependant.

What's wrong with this?

Imagine you have a REST API (openapi) and e.g. a gRPC API. The services do exactly the same, we just have different controllers.

With the current design this forces us to have three layers:

  • the openapi-controller layer
  • the openapi-service layer (HTTP specific)
  • our actual service layer independent of HTTP knowledge.

That middle layer should not be necessary, it bloats up the code.

Describe the solution you'd like

Keeping it implementation-independent, it would be best (also just imo, please enlighten me otherwise) to have service methods like this:

func (s *UserApiService) CreateUser(ctx context.Context, user User) (interface{}, error) {
}

The interface{} is one of the generated model classes, ready for serialization.

Where error can be of a new interface type:

type Error interface {
  Error()

  IsBadUserInput()
  IsInternal()
  IsUpstream()
  // ...
}

Then the controller can check whether the returned error is of that interface and map to corresponding HTTP status codes and if not simply return a 500:

// CreateUser - Create user
func (c *UserApiController) CreateUser(w http.ResponseWriter, r *http.Request) {
	user := &User{}
	if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	result, err := c.service.CreateUser(r.Context(), *user)
	// If an error occurred, encode the error with the status code
	if err != nil {
		if knownErr, ok := err.(Error); ok {
			if knownErr.IsBadUserInput() {
				EncodeJSONResponse(err.Error(), &http.StatusBadRequest, w)
			} else if knownErr.IsInternal() {
				// ...
			}
			// aso.
		} else {	
			EncodeJSONResponse(err.Error(), &http.StatusInternalServerError, w)
		}
	} else {
		// If no error, encode the body and the result code
		EncodeJSONResponse(result, &http.StatusOK, w)
	}
}

Describe alternatives you've considered

Not alternatives, but workarounds.

One workaround is the aforementioned, three-layer-architecture, that bloats up the code.

Another one is ignoring the ImplResponse:

  • either redefine ImplResponse to interface{}
  • or ignore the status code in ImplResponse and only use body

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in the go-server generation path by tracing UserApiService.CreateUser, ImplResponse, and UserApiController.CreateUser. Review how services and controllers currently exchange response bodies, errors, and HTTP status codes. Done would mean an agreed implementation-independent service boundary with corresponding generated server behavior, rather than a design proposal alone.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.