hapipal / boilerplate

A friendly, proven starting place for your next hapi plugin or deployment
https://hapipal.com
184 stars 26 forks source link

The concepts of 'view-model', 'controller', and 'adapter/mapper' are missing from the boilerplate/hpal/ hapipal-realworld-example-app #86

Closed dcominottim closed 4 years ago

dcominottim commented 4 years ago

Not sure if this subproject is the best place for this (maybe it'd be hpal; please let me know), but right now, the aforementioned concepts are missing and that represents a big problem from the separation of concerns PoV.

Right now, what's the best way to create a view-model (say, a REST resource/DTO that is different from the Schwifty model and has different Joi validations, etc.? What about a translation layer responsible for converting between view-model and model? What about the logic inside the handler of a route? (IMHO, it'd be useful to have a controller abstraction for just that, but maybe the expectation is that we'll use a Schmervice service for it and also for adapter/mapper implementations if we want to separate things?)

I know things are usually kept simpler in Node/Hapi projects when compared to, let's say, a Java project, but I can't see a proper separation of concerns without at least the inclusion of the view-model concept.

devinivy commented 4 years ago

I appreciate and agree with a lot of what you're saying here. I believe that it's really important to be able to decouple an application's model from its interface, and that is where these ideas of the view-model and adapters/mappers come into play.

I think the idea of the controller is covered by hapi's own route handlers. Lots of context about these handlers/controllers is provided by a route's configuration: validation and auth config, for example, determine important preconditions for the handlers. I have worked in projects that split the handlers from the route definitions, and it has only created friction. I'm open to new ideas here, but step one is to generate examples and try some things out.

The view-model and mapper is an interesting one. In the case of the realworld application, the API contract is not one-to-one with the way the entities are modeled in the database, so we know this mapping is being done, and the question is just where/how?. For example, the article entity response has a favoritesCount, but we don't store that count as a physical column in the database: we actually query for it then format the result.

I have become fond of writing this "display" logic in one or more services, as you see here: https://github.com/devinivy/hapipal-realworld-example-app/blob/master/lib/services/display.js. A lot of the time the output DTOs aren't simple maps from a model instance, so we split up the display logic into 1. additional queries for more information (objection's fetchGraph() is very well-suited to this) then 2. pure functions that format those results into the DTO. This lends itself to batching queries and doesn't require any special setup or opinionation, so I am a fan of this approach. It's also rather testable!

The other side of mapping is from the input DTO to the model. The input DTO is often subtly different from the output DTO, and again it's commonly not a simple mapping from one object shape to another— accepting one DTO may involve writing to multiple models. In the realworld application that is taken care of right in the service layer itself (see the handling of tagList here for example). I agree that it would be useful to factor this out better and perhaps pair it with some joi validation, and I would welcome more examples and tooling to help deal with this. But I also think that pal provides all the tools necessary for one to provide this factoring themselves.

I am really glad to see someone in the community thinking about these architectural problems that matter a lot to me and within the pal ethos. An important flipside to that part of the pal ethos is to create neutral, baseline APIs and tooling that are extensible and enable folks to architect their applications however they see fit while having support for common/healthy practices. I think that everything you're talking about is important. I would love to see what you come-up with for ideas/examples/prior art/tooling: please share here and in the #hapipal channel of the hapi hour slack if you are so inclined! 🥂