Hanami 2.0 App Structure

Summary

What app components should be in the lib/[app_name] directory? I propose that the mailers and repositories directories be relocated outside of the “core business rules” directory.

Discussion

First, I would like to express how excited I am at the recent progress on Hanami 2. I am stoked at the possibility of using Hanami on a new business project, and I don’t think I will be able to restrain myself from jumping in once 2.1beta1 drops. :crazy_face: Thank you all for your excellent work!

Please bear with me in the following discussion. I want to be complete, at the risk of being pedantic–and a lot of this stuff frankly is new to me.

Hanami 1.x App Structure (for context and comparison)

In Hanami 1.x, multiple “apps” could be created in the apps directory, containing their constituent actions, views, and templates, etc. Meanwhile, the lib directory in the standard Bookshelf project, for example, would look like this:

$ tree lib
lib
├── bookshelf
│   ├── entities
│   ├── mailers
│   │   └── templates
│   └── repositories
└── bookshelf.rb
5 directories, 1 file

I recently built a couple of test projects in 1.3.5 to get my feet wet in anticipation of Hanami 2. In this process, it struck me as strange to have mailers and repositories inside the “core business logic” directory. Following the canonical “clean architecture” pattern, I would only expect to see interactors and entities in this directory (or whatever other organizational pattern you choose, thanks to Hanami’s flexibility!).

Hanami 2.0 App Structure

In the Hanami 2.0beta1 announcement, a slightly modified app structure was introduced (although not fully fleshed out). For simplicity, the default “slice” is removed. Instead, the app directory will directly contain an actions directory (and presumably views and templates), until such time as the app requires further breakdown into slices.

$ tree app
├── app
│   ├── action.rb
│   └── actions

Now, in addition to the bookshelf directory (continuing this example), the lib directory will contain tasks for rake tasks. No mention is made of the proposed locations for mailers or repositories.

$ tree lib
├── lib
│   ├── bookshelf
│   │   └── types.rb
│   └── tasks

Reasoning & Proposals

My interest in this question follows the logic of “clean architecture,” as described by Robert C. Martin. For me, this pattern is best summarized in the following diagram, taken from one of his talks.

The goal of these boundaries is to isolate the business logic from from all of the ‘implementation’ details. In my mind, mailers and repositories should definitely fall outside of the business logic boundary.

So . . . what to do? I can think of two proposals, but neither seem ideal. The first, is to move mailers and repositories to the app folder, at the same level as actions (and, later, the various slices). Like so:

$ tree app
├── app
│   ├── action.rb
│   ├── actions
│   ├── mailers
│   └── repositories

The second proposal is to promote mailers and repositories within the lib directory to the same level as bookshelf and tasks. Like so:

$ tree lib
├── lib
│   ├── bookshelf
│   │   └── types.rb
│   ├── mailers
│   └── repositories
│   └── tasks

I have to admit that neither of these proposals feels immediately natural or correct to me. I realize that entity objects are imported from the ROM-RB library, but I don’t think that should dictate the location of the directories. On the other hand, while mailers and repositories are “implementation” details, putting them in app feels a little weird, especially if the app evolves into slices.

Ideas???

So, I would love to hear what others on the community think about this issue, especially since the app structure for Hanami 2.1 has not been established as of yet. What say you all? :slight_smile: I look forward to hearing your thoughts!

Post Script

One of the reasons that I am interested in this question is the influence that DDD has had on my recent thinking. What would it look like to split a Hanami application into “bounded contexts”? I am thinking more along the lines of a “modular monolith,” such as Vaughn Vernon and others describe, than separately deployed microservices.

It seems likely that these bounded contexts might be sheared along different lines than the slices in Hanami 2. That is, I don’t think there will be a 1:1 conceptual correspondence between slices and bounded contexts, since they server fundamentally different purposes.

Where would repositories be placed in such an architecture? I’m skeptical of having them intermingled with the business rules directly, but they are coupled to some extent with their entity objects, and would conceivably be “bundled” into whatever container is created for the various bounded contexts.

For now I am going to focus on Uncle Bob’s clean architecture style (interactors and entities) and hope that Hanami offers me a pathway to a more modular approach by the time that I need it.

Thank you again for all of your wonderful work!

1 Like

The more I think about my last point regarding bounded contexts developing along different lines than slices, the more I understand the current configuration of having mailers and repositories in the bookshelf directory. If repositories and mailers are going to to be used primarily by the entities and interactors in their parent directory, then it makes sense to have them in the same directory as the interactors and entities.

Does this imply that there could be more than one directory in lib for different ‘bounded contexts’ (in DD terms) or ‘modules’ in Uncle Bob’s terms? That makes a lot of sense to me, but I haven’t seen any discussion of this in the documentation. How would namespacing be handled in such an arrangement in Hanami 2?

@dcr8898 Hey, thanks for your interest and the well structured proposal.

Not final decision yet, but I’m for Option 1: mailers and repositories in app/.

Reason: Hanami 1 had mailers in lib/, which made hard to access assets and helpers from apps/. If they stay in the same context.

Hi, @jodosha. Thank you for your response. :slight_smile:

I don’t have strong feelings about mailers, so I don’t know if their placement should follow the same reasoning as for repositories.

My reasoning for repositories basically follows the Clean Architecture diagram above. The repositories (Robert Martin called them “gateways”) are outside of the core business logic, but they are located on the bottom of the diagram, and not the left with the controllers and presenters, because they are “secondary actors.” According to him, they are invoked by the business logic. Based on this, it makes more sense to me now to keep repositories in the vicinity of the entities they serve. The same idea may or may not make sense for mailers.

One of the things I would like to see for Hanami 2 is the ability to have more than one namespace in the lib directory. Much like slices in the app directory, these could be used to group and organize complex business logic. If you practice DDD, these might correspond to the “bounded contexts” of the business domain. If you follow Robert Martin’s Clean Architecture, they might correspond to “modules” (he sometimes calls them “apps”). These lib namespaces may or may not correspond to the slice namespaces in app.

Brief aside: one of the things I really appreciate about the Hanami 2 alpha is the removal of slices from the default app structure. The same could be done with lib namespaces–add them only when needed.

Since the repositories are closely coupled to the entities used in Hanami 1.x (and presumably in Hanami 2), I think it makes sense to keep them in the same namespace. This would be even more advantageous if Hanami 2 also allowed lib namespaces to utilize different databases or namespaced db tables, possibly with different db users. This would help prevent database coupling between namespaces/bounded contexts/modules.

I see mailers as “secondary actors,” but they differ from repositories in that they do not derive from ROM and function much more like view objects and templates. However, I am curious how mailers would be invoked by interactors, for example (which I hope will be returning to Hanami 2 soon), if they were relocated to app.

Thanks again!

1 Like

I thought about using slices in different ways, and because of how slices can export some components, and because we can load different slices conditionally, I came up with this idea (don’t know yet how it’ll work out).

  1. Use slices based on the interface type
$ tree slices
├── slices
│   ├── api
│   ├── admin
│   ├── web
│   └── client
  1. Use slices based on the domain model
$ tree slices
├── slices
│   ├── ordering
│   ├── publishing
│   ├── booking
│   └── registering

I’m not sure, if allowing nested slices could help us group those different slice types together, but because I can do

# config/slices/admin.rb

module Admin
  class Slice < Hanami::Slice
    import keys: ['interactors.order'], from: :ordering, as: :orders
  end
end

# config/slices/api.rb

module API
  class Slice < Hanami::Slice
    import keys: ['interactors.order'], from: :ordering, as: :orders
  end
end

I could merge those two ideas.

The slice for business domain does not need to even have the view/serializers folder, nor even actions in it. If you follow DDD terminology, you could look there for interactors, commands, events, aggregators, repositories, mailers etc.

For interface slices, you could expect to have some folders, like: views, templates, contracts, serializers.

If we would have a way to create nested slices:

$ tree slices
├── business
│   ├── ordering
│   ├── booking
├── web
│   ├── api
│   ├── admin
│   └── client

Then we could group them and have the file structure visually better.

At the moment, I’m just fiddling around with ideas, however, @dcr8898, I am a huge fan of event-sourced systems, CQRS and DDD and will happily attend all the discussions related to these architectural topics :). Thanks for starting this!

@swilgosz

Slices are probably already flexible enough to use the organization strategy you suggest. This approach is conceptually similar to the Component-Based Rails Application, which orchestrates groups of unbundled Rails engines, each of which can provide any of the usual objects available in a standard Rails app (controllers, views, models, helpers, etc.).

A Gusto team member recently gave a talk at RailsConf describing how they use Packwerk to create “packs” (complete with defined, enforceable interfaces) in a Rails monolith. These packs sound very similar to the way you are suggesting to use slices in Hanami 2.

Much of what I proposed is based on ideas from DDD and Hexagonal/Clean/Onion architecture. The goal of all of these strategies is to isolate the business logic and protect it with clearly defined APIs. I feel that this can best be accomplished by sticking to the Hanami 1.x convention of placing business logic in lib, and by extending the architectural model by providing a pathway to modularization.

@dcr8898 I’ve published a video, where I play with these ideas a little bit in terms of slices maintanance:

I’m curious what will come out of these discussions in a year or two!