Hanami persistence proposal for 2.x

APP structure

app/

app/
  relations/
    coffees.rb       # TestApp::Relations::Coffees < TestApp::Relation
  repositories/
    coffee_repo.rb   # TestApp::Repositories::CoffeeRepo < TestApp::Repository
  structs/
    coffee.rb        # TestApp::Structs::Coffee < TestApp::Struct
  relation.rb        # TestApp::Relation < Hanami::Relation
  repository.rb      # TestApp::Repository < Hanami::Repository
  struct.rb          # TestApp::Struct < Hanami::Struct
db/
  migrations/
  schema.rb

slices/

slices/
  admin/
    relations/
      users.rb         # Admin::Relations::Coffees < Admin::Relation
    repositories/
      coffee_repo.rb   # Admin::Repositories::CoffeeRepo < Admin::Repository
    structs/
      coffee.rb        # Admin::Structs::Coffee < TestApp::Struct
    relation.rb        # Admin::Relation < TestApp::Relation
    repository.rb      # Admin::Repository < TestApp::Repository
    struct.rb          # Admin::Struct < TestApp::Struct
    db/
      migrations/
      schema.rb

ENV Vars & Settings

  • DATABASE_URL: App database
  • ADMIN_DATABASE_URL: Admin database
# config/settings.rb

module TestApp
  class Settings < Hanami::Settings
    setting :database do
      setting :url, constructor: Types::String
    end
  end
end

TestApp.settings.database.url

Note: My suggestion is to use database.url instead of database_url to capture the following use case.

setting :database do
  setting :host, constructor: Types::String
  setting :port, constructor: Types::String
  setting :username, constructor: Types::String
  setting :password, constructor: Types::String
end

TestApp.settings.database      # => { ... } A Hash
TestApp.settings.database.host # => "..." A String
# slices/admin/config/settings.rb

module Admin
  class Settings < TestApp::Settings
    setting :database do
      setting :url, constructor: Types::String
    end
  end
end

Admin.settings.database.url

CLI

Generators

Model

$ bundle exec hanami generate model book
      create  app/structs/book.rb
      create  app/repositories/book_repository.rb
      create  app/relations/books.rb
      create  db/migrations/20230213123250_create_books.rb
$ bundle exec hanami generate model book --slice=admin
      create  slices/admin/structs/book.rb
      create  slices/admin/repositories/book_repository.rb
      create  slices/admin/relations/books.rb
      create  slices/admin/db/migrations/20230213123250_create_books.rb

Migration

$ bundle exec hanami generate migration create_books
      create  db/migrations/20230213123250_create_books.rb
$ bundle exec hanami generate migration create_books --slice=admin
      create  slices/admin/db/migrations/20230213123250_create_books.rb

Database

This is a 2.1 proposal

$ bundle exec hanami generate database --slice=admin --url="postgresql://localhost:5432/admin"
      add     .env                            # ADMIN_DATABASE_URL="..."
      add     .env.development                # ADMIN_DATABASE_URL="..."
      add     .env.test                       # ADMIN_DATABASE_URL="..."
      add     slices/admin/config/settings.rb # Settings block to match ENV var
      create  slices/admin/db/schema.rb
      create  slices/admin/db/migrations/.keep

The option --slice is mandatory

If --url is provided, fill in all .env* files.
If --url is NOT provided, only add ADMIN_DATABASE_URL with an empty string

Database

See 1.x commands for reference

Create

$ bundle exec hanami db create # CREATES ALL DATABASES
$ bundle exec hanami db create --database=app # CREATES App (app/) DATABASE
$ bundle exec hanami db create --database=admin # CREATES Admin (slices/admin) DATABASE

Drop

$ bundle exec hanami db drop # DROPS ALL DATABASES
$ bundle exec hanami db drop --database=app # DROPS App (app/) DATABASE
$ bundle exec hanami db drop --database=admin # DROPS Admin (slices/admin) DATABASE

Migrate

$ bundle exec hanami db migrate # MIGRATES ALL DATABASES
$ bundle exec hanami db migrate --database=app # MIGRATES App (app/) DATABASE
$ bundle exec hanami db migrate --database=admin # MIGRATES Admin (slices/admin) DATABASE

Rollback

$ bundle exec hanami db rollback # ROLLS BACK App (app/) DATABASE
$ bundle exec hanami db rollback 3 # ROLLS BACK App (app/) DATABASE
$ bundle exec hanami db rollback --database=app # ROLLS BACK App (app/) DATABASE
$ bundle exec hanami db rollback 3 --database=app # ROLLS BACK App (app/) DATABASE
$ bundle exec hanami db rollback --database=admin # ROLLS BACK Admin (slices/admin) DATABASE
$ bundle exec hanami db rollback 2 --database=admin # ROLLS BACK Admin (slices/admin) DATABASE

Prepare

$ bundle exec hanami db prepare # PREPARES ALL DATABASES
$ bundle exec hanami db prepare --database=app # PREPARES App (app/) DATABASE
$ bundle exec hanami db prepare --database=admin # PREPARES Admin (slices/admin) DATABASE

Apply

$ bundle exec hanami db apply # RUNS FOR ALL DATABASES
$ bundle exec hanami db apply --database=app # RUNS FOR App (app/) DATABASE

It generates db/schema.sql file

$ bundle exec hanami db apply --database=admin # RUNS FOR Admin (slices/admin) DATABASE

It generates slices/admin/db/schema.sql file

Version

$ bundle exec hanami db version # PRINTS ALL DATABASES VERSIONS
$ bundle exec hanami db version --database=app # PRINTS App (app/) DATABASE VERSION
$ bundle exec hanami db version --database=admin # PRINTS Admin (slices/admin) DATABASE VERSION

Rake

For compatibility with Ruby hosting vendors, we need to expose a db:migrate Rake task

$ bundle exec rake db:migrate # MIGRATES ALL THE DATABASES
$ HANAMI_DATABASE=app bundle exec rake db:migrate # MIGRATES App (app/) DATABASE
$ HANAMI_DATABASE=admin bundle exec rake db:migrate # MIGRATES Admin (slices/admin) DATABASE

@jodosha What is your reasoning behind placing relations in the same place where you have app logic? - Relations are part of persistence layer, and repositories belong to the application layer. Relations are adapter-specific, and map to to the underlaying structure of database(s) - slices structure could be completely different than your db.

I am afraid that with this configuration we could fail into the trap of building applications based on the database schema which is one of the caveats with other frameworks. I feel like keeping db structure independent of slices is important for apps to grow without technical debt increasing too much.

2 Likes

@swilgosz Thanks for the feedback.

Let’s split your concerns.


Relations. Where do you believe they should go?
My thinking was to keep them in app/, alongside with repos and structs. The three of them go together (IMO) and so they have the same location.


Slices. In my proposal, they will share the same App database.

But they can have their own database, so when you build your Modular Monolith, you’re already partitioning the data on a domain basis (slice). In this way you avoid data coupling.


Can you please share with me a structure that you would like to see? Including the reasons. Thanks! :pray:

1 Like

How do you anticipate handling DATABASE_URL in test suites?

I greatly prefer this approach (as opposed to Rails’ database.yml) but it has lead to accidental test runs against the wrong database connection, because changing your env variables for a test run is easy to forget.

My solution to this problem has been twofold: using this helper method in my persistence provider:

def database_url
  uri = target[:settings].database_url

  if Hanami.env?(:test)
    uri = URI(uri)
    uri.path = "/app_test"
    uri = uri.to_s
  end

  uri
end

clearly that won’t do for a framework feature.

The second thing I ended up doing is implementing something similar to Rails’ database protection via a system_metadata table. (This was necessary following a production incident where I accidentally ran DatabaseCleaner on our production database :flushed:)

The database protection feature is good to have as a fail-safe, perhaps something I could upstream.

The same way we did in 1.x: via different .env.* files.

Read more at: :point_down:


Regarding the protection of production database, we already had this in 1.x.

Quoting from the guides:

In order to preserve production data, this command can’t be run in the production environment.

Read more at: :point_down:

Slices:

I’m not sure, unfortunately. I got a feeling that in Hanami 2, /app and /slices are the application layer, where business logic could be put. However, persistence (relations) should be split in case we have different databases - not per slice but allowing to pull data from multiple DBs.

AFAIK one of ROM’s powers is allowing such cross-DB, and cross-adapter even data fetching.

Relations

Having the above in mind, this is why I’m more into keeping persistence (Relations) in the /lib folder. I’m not too strict about it, just thinking loudly.

When you have relations (that correspond to your DB structures), then you can implement repositories, and app-level structs even in the /app or /slices.

To summarize, I’m fine with having repositories and App-specific structs in the app and slices, just relations seem to be sth different to me.

Proposal:

lib
  |-- persistence
    |-- DB1
      |-- relations
    |-- DB 2
      |-- relations 
    |-- File-based DB
    |-- CSV DB
    |-- HTTP based DB... 
  |-- ...
app
  |-- repositories # (common classes)
  |-- structs
  |-- ...
slices
  |-- admin
    |-- repositories # figures out which relations to use?
    |-- queries # objects with advanced queries, across multiple dbs?
    |-- structs

Please forgive me if in my reasoning there is a missing concept of ROM possibilities and assumptions, I’m still ActiveRecord-ish in thinking, don’t feel fluent in understanding all these concepts just yet, for very large or complex apps/domains.

So I’ll comment on this in full soon enough, but in the meantime, I did want to point out one thing.

Over in my example app I’ve just moved my relation classes into app/, as a way to start getting a feel for the structure @jodosha has outlined above: Move relations to app/ by timriley · Pull Request #17 · decafsucks/decafsucks · GitHub

This change required I change my rom auto-registration setup to the following:

    rom_config.auto_registration(
      target.root.join("app"),
      namespace: Hanami.app.namespace.to_s
    )

With target.root.join("app") being given as the auto_registration directory, it means that rom will also look there for mappers/ and commands/ subdirectories, as well as the expected relations/.

Now defining custom mappers and commands is fairly advanced rom usage, but either way, I don’t think we’d want to see these defined in top-level directories under app. Those directories feel much too prominent for what are otherwise fairly niche concerns.

In addition, there’s a fair chance from naming collisions here too: “mappers” and “commands” are fairly common terms and there’s a non-zero chance that a user will try and create directories like those as the namespace for unrelated objects.

I tried passing this option to rom’s auto_registration method:

component_dirs: {
  relations: :relations
}

As well as this:

component_dirs: {
  relations: relations,
  mappers: nil,
  commands: nil,
}

But both of these failed because rom’s auto-registration setup expects these to be defined at all times.

I think this points to the need for rom’s auto-registration setup to be a little more flexible. Ideally I think we’d want the following:

  1. Ability to auto-register relations only in with one given root dir (app/) and namespace (e.g. "AppNamespace::Relations")
  2. And then to separately auto-register mappers and commands with different given root dir and namespace

Implicit in the above is that any for any single rom auto_registration call, it should be possible to disable registration for particular component types, such as mappers and commends.

Then after this, as a separate matter, we’d want to think about how whether we want to support default auto-registration for rom mappers and commands within Hanami apps, and if so, what directories they should use.

I suppose an alternative for Hanami’s rom setup could be to avoid rom’s auto_register entirely, and instead find and manually register all the rom components. But it’d probably be nice if we could make rom’s auto_register more flexible anyway :slight_smile:

cc @solnic since I think these could be useful things to solve with the upcoming version of rom.

I need to revisit this because better auto-registration was one of the reasons of the setup overhaul in rom 6.0. I’ll get back to you once I confirm how this could be arranged in rom 6.0.

@timriley I am wondering, wouldn’t it be way easier if you would have:

app/persistence/relations
app/persistence/mappers
app/persistence/commands
app/persistence/changesets

With the Peristence namespace in place?

I agree, that naming conflicts may be a thing. I often used Commands namespace in my programming style.

Like @swilgosz, I don’t feel fluent enough in ROM concepts and usage to comment in-depth, but I would like to argue strongly for a pathway that allows business/domain logic to be segregated from from the UI–and even from the framework itself.

I applaud the decision not to have a default slice in new apps. This simplifies the early going, while providing a path to more complex apps that may be composed of many slices. However, in my view, these slices are all part of the framework/UI layer: actions, mailers, views (and related objects), templates, etc. These all feel like they belong together, organized neatly with slices. It feels forced to me to say that domain logic could just be placed in its own slice or slices (presumably without the UI components mentioned above).

To me, the greatest beauty of Hanami 1.0 was the respect it gave to developers to organize their domain logic as they saw fit, and the courtesy to provide a place to put that code. It also provided a sane default for domain logic in Interactors and Entities (a pattern that I still favor), but did not force this on anyone.

I don’t want to get too hung up on the specific folder name or location, but I am really hoping that Hanami 2.1 will provide a safe, separate area for developers to grow their domain code as independently from the framework as they may wish. This could be in /lib/TestApp/ or /app/lib/ or /app/TestApp/ or wherever, but it’s presence alone would be the single greatest differentiator from existing approaches in Ruby.

As far as persistence goes, I also agree with @swilgosz:

I am afraid that with this configuration we could fail into the trap of building applications based on the database schema which is one of the caveats with other frameworks. I feel like keeping db structure independent of slices is important for apps to grow without technical debt increasing too much.

While the persistence code artifacts (repositories, relations, etc.) are obviously part of the framework, they are consumed by the domain logic and therefore should be organized alongside the domain logic as closely as possible–and ideally in a way that would allow modularization of the persistence artifacts along with the domain objects they serve. I don’t think it is correct to assume that domain logic will tend to be modularized along the same lines as slices will be. Rather, I would argue that domain logic and UI exist at two different levels and should be free to evolve independently.

Going back to the absence of slices in starter Hanami 2.0 apps, I wonder if it would be possible to keep persistence logic in the app folder for simple apps (as @timriley is doing in decaf sucks). This would allow for fast and easy development new apps, but at the same time would probably not be a strong enough differentiator to win Hanami followers away from Rails. There would have to be some pathway for the persistence logic to move elsewhere alongside the domain logic (once that is fleshed out). Hanami 1 offered something truly novel by providing a pathway to organize, segregate, and modularize domain logic, and I am really hoping that Hanami 2 does too.

I feel that this is what is really missing in the Ruby ecosystem: a framework that encourages modularity of domain logic. Roda and Sinatra are agnostic, and Rails orthodoxy discourages abstractions not included in the framework, leading to the proliferation of poorly defined strategies like service objects. I believe Hanami 2.1 has the opportunity to stake out completely new ground in the Ruby world.

For example, I am no expert in Elixir or Phoenix either, but I appreciate their mantra that “Phoenix is not your app.” See this blog post, for example (citing a conference talk from a Phoenix core team-member):

Where to put business logic?
Phoenix is not your application, so the “application” gets moved outside of the web folder. It rests in the lib/appname of your app folder, and it is what would be called a “model” in other frameworks.
Here are two things that you most likely will include there:

  • Contexts. Basically, Elixir modules that host business logic. This is where you will put stuff that interacts with the database, for example.
  • Schemas. They map data from the database into Elixir structs and vice versa.

By providing a similar, well thought-out solution to this problem in Ruby, I believe that Hanami 2.1 could stake out a strong claim as the framework for professionals concerned with growing maintainable apps “from start-up to IPO” (to steal a phrase)–a story that would have immediately appeal to product developers/consultants and all of those currently struggling with large Rails apps.

2 Likes

Both @dcr8898 and @swilgosz raise important points IMO. Say it boils down to “slices for UI, another layout in lib/ or elsewhare for the rest” versus “slices for whatever part you’d like to wrap”. I may be oversimplifying things, but at least these two ideas could be merged:

On the filesystem, slices/ is just a directory which contains the, well, slices. This name and implementation is generic and a perfect fit for “a slice is any extracted part of the app” which may contain UI, business logic, a mix of both etc. The problem: How to tell the responsability of a slice just from looking at ls slices/?

  • You could prefix the individual slices like slices/ui_admin/ or slices/op_invoicing, but that’s pretty ugly.
  • You could introduce another hierarchy between slices/ and the slice like slices/ui/admin/ or slices/operation/invoicing/. This is a lot of overhead when adding slices to an existing, simple, sliceless app. Also, the middle ground without this additional hierarchy is no longer an option.

But how about making the slices directories configurable?

Currently, there’s either no or exactly one directory slices/, in pseudo code: slices_directories = ["slices"]. Maybe it’s possible to make this more flexible by allowing to customize the slices directories for your app, in pseudo code: slices_directories = ["ui", "operation"] or slices_directories = ["ui_slices", "operation_slices"] – however you like.

(Updated: “operation” instead of “business_logic”)

I know this is an old thread, but I would like to respond to @svoop’s recent post because he raises interesting issues. Also, I feel like my views have matured (somewhat) and I will be better able to articulate them clearly (and succinctly–I hope :roll_eyes: no promises).

I want to pause first to thank the core team for the amazing progress over the past year. I appreciate all of your hard work! I am so excited to see where Hanami will go–and where it will allow me and others to go.

Over the past year I used Hanami 2 to build a tiny production app (a few hundred dollars so far :partying_face:, but I think I still need to find a real job . . .). I followed the patterns laid out in @timriley’s DecafSucks and @bkuhlmann’s Hemo app, and just tried to learn as much as I could. Having done that, I have to say that my original views here and in other threads on ideas for organizing Hanami apps have only been reinforced.

I found Hanami by literally trying various Google searches for Ruby frameworks and “clean architecture” (because those were the only words I knew to describe what I wanted to learn about). This brought me to Hanami 1 during the period that Hanami 2 was still in the planning phase. This should tell you something about my mindset and the path I was looking to explore.

Hanami 1 vs Hanami 2

In Hanami 1, we had a separate home for “business logic”: it lived under /lib/project-name/. In Hanami 2, there is a different approach. There is a special, default “slice” that lives under /app, and one or more other slices (optionally) that live under /slices.

@timriley and the others have put in a ton of work to make it so that anything you can do in /app, you can also do in any of your slices. The real distinction is in the autoloading: everything in /app is available everywhere, code in /app can call anything (including code in slices), but code in slices can only call to other slices where explicitly permitted. (Please correct me if that is not accurate.)

It seems that the recommended convention is to break up your app by placing complete “vertical slices,” or some subset of functionality, into separate slices. This is cool, but I wanted to explore something a little different.

I would like to pursue a pattern that is more consistent with the Hanami 1 approach. I call it “Shell & Core” (or "Core & Shell, depending on how I’m feeling :thinking:) based on Gary Bernhardt’s well-known talk, “Functional Core, Imperative Shell.” I’m not sure that I’m following Gary’s advice exactly, but the name is succinct and describes, in general, what I would like to do. Which is . . .

Shell & Core

I would like to keep all my UI/App kind of stuff in the /app directory. All actions, views, mailers, channels, what-have-you. I think of this as the “shell.” I would like to keep all of my “business logic” (the “core”) in one or more slices. Touching on @svoop’s question, I would name each slice based on its broad area of concern for the domain, like “identity,” “sales,” “claims,” etc. But in the beginning, I just start with a “main” slice.

ASIDE: I love the exploration of modularity concepts that slices allows (and Packwerk allows in the Rails space), but I think that future experience will show that including UI and other “shell” concerns in slices will only lead to coupling and hard-to-maintain code bases.

As for the “core” (i.e., slices), I would like to explore a pattern that I first saw laid out in a wonderful (and underappreciated) talk by Peter Bhat Harkins at RailsConf 2015, “What comes after MVC?.” (Peter is the maintainer of lobste.rs and is/was a consultant.) He argued for splitting business logic into four basic object types: shell (more on that in a sec), values, entities, and adapters.

The values, entities, and adapters all readily map to existing objects in Hanami: types, entities/structs (when persistence is implemented), and providers. I would use the more general term “adapters,” and place the Hanami providers and ROM relations and repositories in this directory, although I haven’t worked out the sub-directory structure for these yet.

By “shell” objects, Peter is referring to command-like objects that orchestrate the other object-types to carry out actual work. In Hanami 1, this role was filled by Interactors. In Hanami 2, I would guess that this role will be filled by the dry-operation project (which appears to be the spiritual successor to dry-transaction and the dry-monads “Do Notation”).

Since dry-operation is not yet ready, I hacked together a “proto-interactor” to fulfill this roll. The name of the object is totally arbitrary, but what I really wanted was the “Params” behavior of Hanami Actions, the Do Notation of dry-monads (to do my work inside of the ProtoInteractor), and the ability to return a dry-monads result object.

That turned out to be pretty easy to put together. I am now (just starting, actually) using this object and Peter Harkins’ patterns to refactor my tiny app (before growing it further). The resulting folder structure could look something like this:

slices/
├── identity/
│   ├── adapters/
│   ├── entities/
│   ├── types/
│   ├── identify_member.rb
│   └── ...
├── sales/
│   ├── adapters/
│   ├── entities/
│   ├── types/
│   ├── [interactor].rb
│   └── ...
└── claims/
    ├── adapters/
    ├── entities/
    ├── types/
    ├── [interactor].rb
    └── ...

In this way, the “use cases” (ProtoInteractors in my case) of each slice would be listed right in the root directory of the slice. If a slice grows out of hand, the interactors and other objects could be further sub-grouped in namespaces within the slice–think multiple DDD-style aggregates in a slice/domain.

How I (am trying to) use it

Like this:

  • Actions (or whatever other endpoint) in /app call one or more interactors (two to four is probably typical) to retrieve data or make changes, and use that information to execute views.
  • Each slice (which could potentially have it’s own database–I haven’t actually tried that yet) would be constrained to it’s assigned area of responsibility.
  • I can use HTMX to separate out portions of a page to call their own REST endpoints so actions don’t need to be so busy collecting all the data for every page–my version of “islands.”
  • I want my actions to be almost as boring as routing tables (similar to the pattern @solnic tooted about recently).

All of this is possible through existing Hanami/Zeitwerk autoloading.

Wrap up

I don’t know if @svoop’s suggestion of multiple, custom-named slice directories is feasible (it probably is), but I don’t think I would need it for the pattern described here. I might like to rename /slices to /core, but that’s hardly necessary.

I am very excited about my current explorations. I think this pattern could be very useful and could provide an answer for dealing with increasing complexity in large/growing apps that is currently lacking in the Ruby ecosystem. I haven’t sorted out all of the details yet, and I’m leaving some things out here for brevity (if you can call it that :roll_eyes:). I plan on writing one or more blog articles with more details on this approach but, you know, time . . . :unamused:

I am certainly not advocating for this pattern for every Hanami app, or any new app at all for that matter. I love the flexibility that Hanami 2 currently offers. For small or new apps, keep everything in /app! Relations, repositories, the whole bit! Handle your params and business logic right in the actions (or other objects) and rock out! However, when you start to grow, I think the patterns laid out above by smarter people before me are really compelling–and Hanami can also take you there!

A final note on managing UI complexity, I think namespaces under /app are more than up to the task (and that modularizing UI is probably overkill). You could easily have something like:

app/
├── members/
│   ├── actions
│   ├── views
│   └── mailers
├── member_admins/
│   ├── actions
│   ├── views
│   └── mailers
└── admin/
    ├── actions
    └── views

I think this would currently break Hanami generators and routing helpers, but I believe it is already compatible with autoloading.

Those are my thoughts. Thank you for reading this far. :relieved: If any of this sounds interesting to you, I would love to discuss these ideas with someone (okay, anyone :laughing:).

It’s great to see all this discussion here, folks! FYI, setting up persistence is back on my radar now that Hanami 2.1.0 is out the door. I probably won’t work on this in earnest until next month (after Ruby in Common is over), but in the meantime, let me clarify a few things here.

This is not accurate. :wink:

The app, as you noticed, is just another a slice, with a very small number of specialisations. And only one of these has anything to do with access to things outside the app (or any slice’s) container or Ruby module.

Let’s break things down along those two lines.

If we’re talking about containers, then components within the app or slice only have access to the components registered within that slice (via the slice’s Deps mixin, to be clear).

In general, components registered in app are not available anywhere else. There is a small list of components (configured in the app’s config.shared_app_component_keys) that are automatically imported into each slice from the app. These are ["inflector", "logger", "notifications", "rack.monitor", "routes", "settings"].

Aside from this, the app and slices behave the same in terms of accessing each other’s components. The way to do this is to create your own explicit exports and imports. See the relevant docs for details.

Now if we’re talking about constants, there’s nothing we do to try and stop you from directly accessing constants outside the app or slice boundaries. This is all just Ruby. You can type whatever you want, and as long as the slice is loaded (i.e. it’s not excluded from loading based on config.slices or HANAMI_SLICES config, per these docs), then the slice’s constants will be autoloadable and accessible.

Slices are a general purpose code organisation tool and we’re happy for people to use them as they wish. We certainly support “vertical slices”, because not doing so would eliminate a valid use case :slight_smile:

Thank you for correcting my errors! I’m learning as I go. :nerd_face:

Are you able share any further insights into the future plans around this? I know you guys have a prototype of dry-operations. Will that have a role in Hanami? Are you thinking about a Hanami convention for the content of slices that is something other than a full “vertical slice”?

Provided we get dry-operation done in time, I hope to at least have this included in the default Gemfile for new hanami-apps, and possibly have some default files and/or a generator that makes it easy to make new operation classes.

Exact details are TBD, but that’s my rough plan.

I’m not sure if a “convention” is even required here? Slices are just directories that hold your code. You can choose what goes in there.

So right now you should already be able to create slices that are as heavy or as light as you like. If there’s anything that gets in the way of you doing that, I’d love to hear it!

This sounds like a powerful paradigm to me leaving many ways to organize the code depending on the nature of the app. This said, what do you think about allowing custom named slice directories proposed further up this thread, worth a thought or bullocks?

Thanks again for following up here. Any information is helpful.

I love that slices are just containers for your code. I think this is a huge differentiator from Rails and other frameworks, and is the leading selling point for Hanami.

I think conventions are important though. Users love their conventions–and their generators. They’re like a security blanket. :slightly_smiling_face: They also provide the uniformity that allows developers to go from project to project (or job) and be confident that they will recognize the “Hanami Way.”

I say this just as food for thought. The current slices generator creates a full scaffold of actions, views, etc., in the new slice (and corresponding tests). This might just be for learning purposes, to orient new users, but it also conveys the impression of a convention.

Thanks again for your work and your feedback.

1 Like