← Back to posts
Published Edit

Composable domain models

5 Nov 2026

Entities are identity; components carry the state and behaviour. It reads like plain Rails.


How this works

♥ Like runs post.likes_counter.increment!. The first like inserts a counters row in slot likes, and each later like updates it. Before the first like there is no row at all: the count you see is the column default, 0.

Publish now transitions the State in slot publish. The author is a row in the shared relationships table, not a column on the post.

The declaration · app/entities/post.rb

component Text,    prefix: :title                              # post.title (the String), post.title_text (the Text)
component Text,    prefix: :body                               # post.body
component State,   prefix: :publish, states: %w[draft published] # post.publish_state.status
component Counter, prefix: :likes                              # post.likes (the Integer), post.likes_counter
component SearchVector                                         # rebuilt by Demo::Indexer from the post's Texts
relates_to :author, User                                       # post.author => User
has_many :comments, via: :post, dependent: :destroy            # post.comments

The rows behind it

WhereRow
entities
  • id = 2618346d…
  • model = posts
texts slot title
  • value = Composable domain models
texts slot body
  • value = Entities are identity; components carry…
states slot publish
  • status = published
  • transitions = []
counters slot likes
  • count = 3
search_vectors default slot Not read by this page, so not queried
relationships slot author
  • exclusive = false
  • owner_model = posts
  • target_id = 7b37fc34…

Every slot row also has id, entity_id (this entity's id) and timestamps. Ids are cut to their first block. The rows are the ones this page loaded, so the panel adds no queries.

Comments · 2

GH
Grace Hopper about 2 hours ago

This finally makes the pattern click.

AT
Alan Turing about 2 hours ago

Reuse without inheritance — elegant.


Commenting as Ada Lovelace. Change who you are acting as at the top of the page.