Proper software architecture is key to create a project that is maintainable. What proper means is 100% subjective, but lately I like and try to follow Clean Architecture by Robert C. Martin (aka Uncle Bob).
Although I really like the theory, it lacks some sort of practical implementation guide for common technical challenges developers may face. One of the things I've been struggling with for example is properly implementing the presenter layer.
The presenter is responsible for accepting the "response" from my use case and formatting it in a way that it can be "presented" to my output device (regardless if it is a web or a CLI application).
There are multiple approaches for this problem, but they usually fall under one of these categories:
- The presenter is called by the use case itself through some sort of output interface
- The use case returns the response model and the controller (which originally called the use case) passes this model to the presenter
Option 1 is more or less the same as what Clean Architecture/Uncle Bob says (in the book and in various posts, see later), Option 2 is rather an alternative approach which works.
Sounds cool, but let's see how we can implement them in Go.
Here is my first version. For simplicity, our output goes to the web now.
Also, please excuse my brevity.
package my_domain
import "http"
type useCase struct {
presenter presenter
}
func (uc *useCase) doSomething(arg string) {
uc.presenter("success")
}
type presenter interface {
present(respone interface{})
}
type controller struct {
useCase useCase
}
func (c *controller) Action(rw http.ResponseWriter, req *http.Request) {
c.useCase("argument")
}
Basically it does exactly as described above and in Clean Architecture: There is a controller which calls a use case (through a boundary, which is not present here). The use case does something and calls the presenter (which is not implemented, but it's exactly the question).
Our next step could be implementing the presenter....but given how output works in Go HTTP handlers there is a nice problem to solve. Namely: request scope.
Every request has it's own response writer (passed to the http handler) where the response should be written. There is no global request scope that can be accessed by the presenter, it needs the response writer. So if I want to follow option 1 (use case calling the presenter), I have to pass it somehow to the presenter which becomes request scoped this way, while the rest of the application is completely stateless and not request scoped, they are instantiated once.
That also means that I either pass the response writer itself to the use case and the presenter (and I would rather not do that) or create a new presenter for each request.
Where can I do that:
- In the controller via factories
- In the use case via factories (but then again: the use case would have to receive the response writer as a parameter)
This bring in another problem: if the presenter is request scoped, is the use case too?
If I want to inject the presenter into the use case struct, then yes it is and the use case has to be created in the controller as well.
Alternatively I can make the presenter a parameter of the use case (noone said a dependency must be injected at "construction time"). But that would still somewhat couple the presenter to the controller.
There are other, unanswered issues (like where should I send HTTP headers for example), but those are less Go specific.
This is a theoretical question as I'm not yet sure that I want to use this pattern, but I've spent quite an amount of time thinking about this problem without finding the perfect one so far.
Based on the articles and questions I've read about the topic: others haven't either.