Beyond Static Models: Behavior-First Design for Changing Domains

Why Your ERD Is Not the Domain and How Events Make Systems Explainable

Beyond Static Models: Behavior-First Design for Changing Domains
Image Credit: stnazkul

I want to share some thoughts that emerged from the discussions around my recent articles over the past few weeks.

“I don't quite follow the ‘behavior first’, ‘event sourcing fits everywhere’ perspective. Honestly, you are using quite a large brush and generalizing way too much.”

That pushback is healthy. “Events everywhere” becomes a mantra if it’s not anchored in purpose. The goal is not to sprinkle events over code and declare victory. The goal is to build systems that explain themselves, systems where decisions, constraints, and outcomes remain visible long after the current state has changed.

This article unpacks what behavior-first actually means, contrasts it with structure-first modeling, and shows how to handle very practical needs like access control, interdependent fields, and analytical reporting without collapsing back into a monolithic “proper data model” that erases time and intent. The position is pragmatic: Event Sourcing is a tool; behavior-first is a mindset. Both are useful when they illuminate causality and make change safer. Neither is pixie dust.

The Misread: Behavior-First ≠ Events-Everywhere

“Events are not pixie dust. You can take any backend API and trivially make it event driven… What kind of value are you providing with that, though?”

Agreed. Turning method calls into “events” on a bus adds ceremony without adding meaning. Behavior-first is not about shoving messages through a queue. It’s a shift in what the system chooses to remember.

  • Structure-first systems center on what is: the latest shape of data in tables or objects.
  • Behavior-first systems center on what happened: the sequence of decisions and the facts that followed.

Events are the record of those decisions and facts. They exist to carry intent (“what was attempted, under which rule?”) and outcome (“what changed, with which consequences?”). Whether that record lives in an append-only event store, an audit log, or a hybrid depends on context. The value comes from the narrative the system can tell (the chain from intent to effect) not from the transport mechanism.

The trap of “proper” models

“Events are not pixie dust. You can take any backend API and trivially make it event driven: any method call becomes an event and every response goes back to the same bus. What kind of value are you providing with that, though?”

Turning method calls into “events” does not create value. Value appears when a system keeps the intent and decision that created state, so it can evolve without surgery.

“Proper data models” are great for well-understood, stable domains. They are not great during discovery. They push teams to decide too much up front. They entangle rules, permissions, and reporting into one central shape. They create migration work before learning has even started.

Quick distinction: event-sourced ≠ event-driven

  • Event-sourced is a persistence pattern. The source of truth is an append-only log of domain events. State is derived via projections or snapshots. It is about keeping time and decisions.
  • Event-driven is an integration style. Components communicate via messages (events, commands) over a broker to decouple and scale. It is about coordination between parts.

You can be event-driven without event sourcing (publish messages, store only snapshots). You can be event-sourced without being event-driven (single service, no broker, projections in the same process). Conflating them leads to “eventifying” RPC or pushing bus messages into places where a clear decision log would suffice.

Behavior-first flips the order:

  1. Start with a capability expressed as a command.
  2. Encode the rules that make the decision.
  3. Record the fact of what happened.
  4. Project state into whatever tables or documents are needed right now.
  5. Add more verbs and views as the domain clarifies.

This delivers business value immediately and keeps options open.

The Project Management Example, Reworked

“Let’s consider a project management system.”
“What happened? We created a project, with 62 fields.”
“Why did it happen? Because user wanted a new project.”
“What was the intent? Creating a project.”
“Consequences? New project exists, and now has to appear in 76 reports each with 23–42 columns.”

This is an excellent example because this is the big-upfront-schema mindset. It assumes the world on day one is known, stable, and reportable in all the ways stakeholders might ask for later. Reality disagrees.

Here’s a behavior-first reconstruction that keeps business value front and center.

  • Command: OpenProject (initiator, provisional_name)
    Decision surface: “Is this user allowed to open projects?” That is it. No contracts. No budgets. No cross-field constraints. Not in v0.
  • Event: ProjectOpened (project_id, initiator, at, provisional_name)
    The fact recorded is minimal and honest. The project is open. Future actions can enrich it.
  • Projection: CurrentProjects (project_id, name, opened_by, opened_at)
    This is the list screen product needs tomorrow. It exists in an operational table for fast reads. It can be rebuilt any time from events.

That is a complete end-to-end slice. It is shippable. It unlocks feedback. It does not require a complete model. It gives a safe path to add the rest.

Evolve with verbs, not fields

When new knowledge arrives, add verbs that express an actual decision, not just a schema change:

  • RenameProject → ProjectRenamed (name_before, name_after, reason)
  • AttachContract → ContractAttached (contract_ref, version, hash)
  • AllocateBudget → BudgetAllocated (amount, currency, source)
  • SetAccessPolicy → AccessPolicySet (rules)
  • DefineMilestone → MilestoneDefined (date, scope)
  • ApproveMilestone → MilestoneApproved (approver, at)

Each verb has a decision surface: the minimal facts required to say yes or no. Keep the surface small. Keep rules explicit and close to the command. Test them as pure logic.

State is still derived. The “project record” in a table becomes a projection of decisions. It looks like a familiar row, but it is not the source of truth and it does not need to be complete to be useful.

Interdependent fields live in rules, not tables

“Very often, these systems need detailed content (what’s in the contract, interdependent fields).”

Interdependent fields signal policy, not structure. Place the policy on the command path:

  • “A project with a termination date must include a termination clause.”
    Checked when attaching a contract or setting dates.
  • “Budget increases above threshold require a role with approval rights.”
    Checked when allocating budget.
  • “Start date cannot move before the earliest approved milestone.”
    Checked when redefining milestones.

No schema migration is required to change a rule. Future decisions follow the new rule. Past decisions remain truthful. This is agility in practice, not in slide decks.

Access control as a capability, not boilerplate

“Actual access control (who can do what).”

Treat access control as a first-class capability:

  • Commands carry who attempts the action.
  • A policy layer decides whether that actor may do it, given current projections.
  • Access changes are verbs too: SetAccessPolicy, GrantRole, RevokeRole.

For read-time checks, maintain a tiny projection of effective permissions. It is a small table keyed by principal and resource that answers “can X do Y here” in O(1). It is derived from AccessPolicySet and similar events. It evolves as the org chart and rules evolve.

Reporting without the 76-report trap

“Analytical reporting (pieces of contract data end up in various places).
Consequences? New project exists, and now has to appear in 76 reports each with 23–42 columns.”

Reports multiply when the central model tries to serve everyone. Behavior-first narrows the blast radius:

  • Create report-specific projections that flatten exactly what a report needs.
  • Emit them to a warehouse or keep them local.
  • Refresh on the cadence the report requires.
  • When definitions change, replay to rebuild from the same history.

No master table has to bend to 76 shapes. Each report gets its own small, proper model. Adding a new one is a matter of projecting known facts, not negotiating schema politics.

The agility you actually feel

Behavior-first gives day-to-day agility in concrete ways:

  • Start now with one verb. No “schema committee.”
  • Deliver thin vertical slices that include a decision, a fact, and a small view.
  • Refactor rules without rewriting history.
  • Change read shapes without rewriting writes.
  • Onboard new consumers by adding projections, not touching the core.

You ship earlier. You decide later. You keep options open.

Addressing the core objection

“How are you going to make this without a proper data model?”

By deriving proper models per purpose instead of pretending one model can serve all purposes.

  • Operational screens read from an operational projection.
  • Permissions read from a permissions projection.
  • BI reads from report projections.
  • History is always available because decisions were recorded, not only their end state.

There is still modeling here, but it is local, small, and reversible. It happens when needed, not months in advance.

A short, practical playbook

This helped me a lot when building real-world systems.

  1. Name the first three verbs that unblock value. Example: OpenProject, RenameProject, AttachContract.
  2. Write the decision rules as pure logic. Keep each rule visible and testable.
  3. Record the facts of accepted decisions. Keep events small and meaningful.
  4. Project the smallest useful view for the UI. A table with five columns is fine.
  5. Add one policy on the command path (permissions or a simple invariant).
  6. Deploy. Collect feedback on vocabulary and flow.
  7. Add the next verb. Let verbs evolve the language of the system.
  8. Introduce report projections only when a report is real and needed.
  9. Refactor projections freely; they are caches of answers, not the truth.
  10. Measure where lag matters; keep critical reads synchronous if necessary.

This is the opposite of big-upfront anything. It is also the opposite of chaos. Decisions stay explicit. Effects stay traceable. Structure stays light.

What to avoid (and why)

  • Eventifying RPC: renaming function calls to “events” without modeling decisions. No value here.
  • Snapshot events: recording “ProjectUpdated” with the whole blob every time. Save the change and the reason, not the object.
  • One “domain model” to rule them all: forcing UI, security, and BI to share a single table set. That is where agility dies.
  • Leaking reads into writes: deciding on a command based on convenience fields meant for UI. Keep the decision surface minimal and principled.

Where audit fits (and why it is not the point)

A clean audit trail falls out for free when decisions are recorded. That can be useful. But:

It is NOT the goal.

The goal is ability to change direction without breaking what shipped yesterday. Behavior-first gives that by separating concerns along natural seams: verbs and rules on the write side, shapes and speed on the read side.

Back to the critic, one more time

“Events are not pixie dust… When does it stop being a self-indulging exercise and provides either business value or some kind of implementation quality?”

It stops being indulgent the moment a team ships OpenProject tomorrow instead of debating a 62-field record and 76 reports. It becomes valuable when the next change is a new verb and a small projection, not a migration and a freeze. It improves implementation quality because rules read like rules, not like triggers hidden in a schema.

“Very often, one needs just a basic audit history, if any.”

Sometimes yes. Then just do that. Behavior-first doesn’t force Event Sourcing on every form. It asks for verbs before fields and facts before snapshots so the design can move as fast as the business.

“How are you going to make this without a proper data model?”

By recognizing that “proper” is contextual. Keep the core as decisions and outcomes. Let each consumer have the proper model it needs, projected from the same truth.

Closing: True agility is verbs first

Big-upfront models feel solid until change arrives. Then they become anchors. Real agility looks smaller and moves faster:

  • Start with one verb that matters.
  • Decide with minimal context.
  • Record what happened.
  • Project only what you need.
  • Grow the language of verbs as you learn.

If the first feature of your project management system is OpenProject, you can ship in days. If the first feature is a “proper” 62-field schema and 76 reports, you can ship in quarters.

Pick the path that leaves room to learn.

Cheers!

Subscribe to Rico Fritzsche

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe