Hanami v2.0.0.alpha2 app template feedback

I was thinking about shipping rom (the gem) with built-in system components. So then, in hanami, you’d be able to just do something like this:

# config/boot/persistence.rb
Hanami.application.register_bootable(:persistence, from: :rom) do |container|
  after(:init) do
    container["rom.config"].plugin(:sql, relations: :auto_restrictions)
  end
end

In the most simplistic use-case, this would just work too:

require "hanami"

module Bookshelf
  class Application < Hanami::Application
     # this would result in having container["rom"] registered
     # based on DATABASE_URL setting
    register_bootable(:persistence, from: :rom)
  end
end

I also need to figure out how to pre-configure instrumentation, that’s a bit tricky because notifications object that’s used for instrumentation is provided by hanami.

This will go away :slightly_smiling_face: I’ll be adding support for pre-configured structs directly to rom core. BTW I’m for renaming Entity => Struct and sticking to it. This can be of course configurable, but I would vote for using Struct as the default name because Entity can be confused with stuff from DDD where identity is based on a key value (IIRC).

I actually wonder why is that needed? With HANAMI_ENV set, settings will load .env and then .env.#{env_name} if present. This means that default settings will be overridden by env-specific settings.

but what if we want to change more than just .env based settings, like insert additional middlewares etc?

1 Like

@solnic What @parndt said: there is more than just settings based configurations that you may want to apply.

I think this speaks to the importance of finding the right terminology to clearly separate app settings (the user-defined settings loaded from .env* files) from app configuration (the framework defined settings for controlling its built-in behaviours).

In fact, you can actually use the former to configure the latter, e.g.

module MyApp
  class Application < Hamami::Application
    # In this context:
    # - `settings` are the app _settings_ (user-defined)
    # - `config` is the app _configuration_ (framework-defined)

    config.sessions = :cookie, {
      key: "my_app.session",
      secret: settings.session_secret,
      #### ^ App setting being used as an app configuration value!
      expire_after: 60*60*24*365
    }

Am definitely open to ideas from the community about clarifying the naming here.

@parndt @jodosha OK I still want to challenge this but I don’t consider it to be important for 2.0.0 final :slightly_smiling_face:

I think it’s gonna boil down to clear documentation. It’s in-line with dry-configurable terminology too, so at least there’s that. Settings as a concept doesn’t exist in Rails though so folks coming from Rails might be confused :man_shrugging:

1 Like

I disagree here, the benefits are greater than the effort to make this to work.

def environment(env, hanami_env: Hanami.env)
  yield if env == hanami_env
end
module MyApp
  class Application < Hanami::Application
    config.environment(:production) do
      config.use(ExceptionTrackerRackMiddleware)
    end
  end
end

@solnic You’re right - but some gems could not for example provide in-memory adapters for testing. In that case, it could make sense to skip the configuration at all.

But on the other hand, I can just have a dedicated logging file and keep the configuration simple.

Also rename the CsrfProtection => CSRFProtection - with zeitwerk configured so it recognizes classes named this way.

Let’s Unify folders:

slices/main/lib/main/views
slices/main/lib/main/view

Into single:

slices/main/lib/main/views

It would be much more clean to have: Views::Base, Views::Show . For me it seems confusing a bit to have two identical folders next to each other, one in singular one i plural, but both responsible for the same building block of the system

Flatten slices structure:

Current:

⚡ tree slices/main
slices/main
├── assets
│   └── public
│       ├── entry.js
│       ├── index.css
│       └── index.js
├── lib
│   └── main
│       ├── action.rb
│       ├── actions
│       │   └── home
│       │       └── show.rb
│       ├── entities
│       ├── entities.rb
│       ├── repository.rb
│       ├── view
│       │   ├── base.rb
│       │   ├── parts
│       │   └── parts.rb
│       └── views
│           └── home
│               └── show.rb
└── web
    └── templates
        ├── home
        │   └── show.html.slim
        └── layouts
            └── application.html.slim

15 directories, 12 files

Proposed:

⚡ tree slices/main
slices/main
├── action.rb
├── actions
│   └── home
│       └── show.rb
├── assets
│   ├── javascripts
│   │   ├── entry.js
│   │   └── index.js
│   └── stylesheets
│       └── index.css
├── entities
├── entity.rb
├── lib
├── repository.rb
├── templates
│   ├── home
│   │   └── show.html.slim
│   └── layouts
│       └── application.html.slim
├── view.rb
└── views
    ├── home
    │   └── show.rb
    ├── parts
    └── parts.rb

13 directories, 12 files

Suggested changes:

  • Remove slices/main/lib/main, as it’s redundant IMO
  • Keep slices/main/lib (with .gitkeep) for custom logic.
  • Move at the root of the slice the default base types: action.rb, entity.rb, repository.rb, view.rb (e.g. slices/main/action.rb to define Main::Action).
  • Move at the root of the slice the corresponding directories: actions, entities, repositories, views.
  • Remove slices/main/web.
  • Move templates under slices/main/templates
  • Move view parts under slices/main/views/parts and slices/main/views/part.rb for the base part.
  • BONUS: create slices/assets/{javascripts/stylesheets} and remove slices/assets/public
2 Likes

@solnic I’m not sure it’s the right change.


Struct

Definition by Ruby doc:

A Struct is a convenient way to bundle a number of attributes together, using accessor methods […]

Definition by Wikipedia:

A struct in the C programming language (and many derivatives) is a composite data type (or record) declaration that defines a physically grouped list of variables under one name in a block of memory, allowing the different variables to be accessed via a single pointer or by the struct declared name which returns the same address. The struct data type can contain other data types so is used for mixed-data-type records such as a hard-drive directory entry (file length, name, extension, physical address, etc.), or other mixed-type records (name, address, telephone, balance, etc.).

Definition by TechTerms:

A struct (short for structure) is a data type available in C programming languages, such as C, C++, and C#. It is a user-defined data type that can store multiple related items. A struct variable is similar to a database record since it may contain multiple data types related to a single entity.

[emphasis is mine]

Struct is a data (struct)ure.


Entity

Definition by The Java EE 5 Tutorial:

An entity is a lightweight persistence domain object. Typically an entity represents a table in a relational database, and each entity instance corresponds to a row in that table. The primary programming artifact of an entity is the entity class, although entities can use helper classes.

The persistent state of an entity is represented either through persistent fields or persistent properties. These fields or properties use object/relational mapping annotations to map the entities and entity relationships to the relational data in the underlying data store.

Definition by Wikipedia DDD article (which is taken from the book):

An object that is not defined by its attributes, but rather by a thread of continuity and its identity.

Definition by DDD Book (pp. 91):

An object defined primarly by its identity is called an ENTITY.

[emphasis not mine]

Entity is a data structure that represents a database row and has the concept of identity (database primary key).


How this applies to Hanami?

We have repositories that expose database operations. Each of those operations accept data as input and return an object as output.

For bulk operations (e.g. read with filtering), we return a collection of those objects.

Each object represents a database table row (or database document). Each object has the identity semantic (the database primary key).

IMO we should call these objects Entities.

1 Like

Thanks for all the links, it’s very helpful to have such context. I think this deserves a completely separate thread though. Long story-short is that it is not true that these objects are always identified by a primary key. This makes me think that in rom 6.0 we should introduce a dedicated class that represents such a concept because a result of users.by_pk(5).one is different than user.by_pk(5).select(:name, :email).one - the latter returns an instance of a different class (unlike AR).

This is a pretty fundamental difference and something that the docs should explain clearly.

I still think “structs” is a better name, it’s more flexible. “Entities” word comes with too much baggage and various assumptions, the PK/identity being the biggest one. This all can be configurable but it would be good to come up with defaults that we can all agree to.

What are you thinking for Hanami::Operation? I like the name :slight_smile:

Would it be built on dry-transaction, or dry-monad, or Hanami::Interactor? (Maybe renaming dry-transaction, since there’s overlap with dry-monad's Do notation, could work). For reference: Plans for 1.0.0 · Issue #127 · dry-rb/dry-transaction · GitHub

It seems I re-implemented it in for Hanami 2 a couple of years ago. :smiley:

@cllns I was just thinking to take the code from the app template and move it into hanami.

I had a look at dry-system and hanami, it’s much easier to expose a method Hanami.shutdown to be used in Puma before_fork.

# config/puma.rb

before_fork do
  Hanami.shutdown
end

The implementation should hook into Dry::System::Container#shutdown! which iterates for each registered bootable component and triggers the stop block.