← Back to Blog

How to manage complex business logic as one model

business-logicdomain-modelingmcpclaude-codeopen-sourceappling
How to manage complex business logic as one model

Complex business logic stays manageable when it lives in one place that a machine can check. Write the rules down once as a model: what exists, what can happen, who may do it. Then draw every diagram from that model and let every question about the logic go to it first.

This post shows how, with a small invoice approval flow as the example. The tool is Appling, an open source modelling language we sponsor and build our applications with. It is early (0.x), MIT licensed, and already usable for the steps below.

Why business logic gets out of control

Business logic rarely lives in one file. A status field sits in the database model, the permission check in a controller, the deadline in a cron job, and the “hide this button” rule in the frontend.

Each piece is correct on its own. Nobody can see the whole.

So a simple question like “who may approve an invoice, and from which state?” means reading four files and asking two people. Every new feature adds a fifth place.

flowchart LR
    subgraph Scattered["Logic spread over the code"]
      direction TB
      A1["models.py<br/>status field"]:::bad
      A2["views.py<br/>permission check"]:::bad
      A3["cron.py<br/>expiry job"]:::bad
      A4["Invoice.tsx<br/>button rule"]:::bad
    end
    subgraph One["Logic in one model"]
      direction TB
      M["billing.appling"]:::good --> V["validated"]:::good
      M --> D["drawn"]:::good
      M --> Q["queried"]:::good
    end
    Scattered --> One
    classDef good fill:#494fdf,stroke:#376cd5,color:#ffffff,stroke-width:2px
    classDef bad fill:#1e293b,stroke:#64748b,color:#f1f5f9,stroke-width:1.5px

Describe the logic once, as a model

A business logic model captures the rules, not the implementation. Four questions cover most of it:

  1. What exists? Entities, their fields, the allowed values.
  2. What can happen? Actions, state changes, scheduled jobs.
  3. Who may do it? Roles and policies.
  4. How is it reached? APIs and pages.

Appling sorts these into five layers, from Domain (what exists) up to Experience (how people reach it). References only point down the layers. That one rule keeps a growing model from turning into the same tangle as the code.

The model is plain text. It lives in git, goes through review, and diffs like code.

Visualize business logic from the model

A whiteboard diagram is out of date the day after the meeting. A diagram drawn from the model cannot drift, because it is computed from the same text every time.

Here is the invoice lifecycle we will model below. Each arrow is an action, and the model says who may run it:

flowchart LR
    draft -->|Submit<br/>Accountant| submitted
    submitted -->|Approve<br/>Manager| approved
    submitted -->|Reject<br/>Manager| rejected
    approved -->|MarkPaid<br/>Accountant| paid
    classDef state fill:#494fdf,stroke:#376cd5,color:#ffffff,stroke-width:2px
    class draft,submitted,approved,rejected,paid state

In Appling you do not draw this by hand. The IDE and the MCP window render it from the model.

Appling IDE: the model text on the left, the model canvas in the middle unfolded from the sales module to the Order entity and its actions, the inspector on the right

The canvas grows from the modules upwards and unfolds only what you click: here the Order entity of the sample sales model, then the actions that work on it. Every click also selects the source text on the left.

Keep business logic observable

Runtime observability (logs, metrics, traces) tells you what happened. Logic observability tells you what is allowed to happen, before anyone runs the code.

With a model you can ask, at any time:

  • Can an invoice reach paid, and through which steps?
  • Which roles may trigger Approve?
  • Which rules does an action put at risk?
  • Where in the code is this rule implemented?

Appling answers the first three with its simulator and the fourth with code references: a file and line range per element, which follow the code when lines move. The answers come from the model, so they hold for every implementation of it.

In the IDE these answers sit in the inspector. For a state machine it lists every reachable state with the steps that lead there:

Appling IDE inspector for the OrderFlow state machine, listing the reachable states draft, placed, paid and cancelled with the actions that lead to each

For an action it shows what the action requires, what it changes, which transition it drives, and where it is used:

Appling IDE inspector for the PlaceOrder action, showing its precondition, the field it sets, the transition draft to placed and the event it emits

Write your first Appling model

You need Node.js 24 or later. The whole setup takes about ten minutes. The installation guide lists the extra requirements for code generation.

1. Install the CLI and create the model folder.

Terminal window
npm install --save-dev @appling/cli
mkdir models

2. Write the first model file. Create models/billing.appling:

module billing {
role Accountant
role Manager
actor human Clerk roles Accountant
actor human Director roles Manager
enum InvoiceStatus {
draft
submitted
approved
rejected
paid
}
entity Invoice {
number: String unique
amount: Money
status: InvoiceStatus state default draft
invariant "amount is positive" { amount > 0 }
}
action Submit on Invoice {
requires { amount > 0 } else ZeroAmount
errors ZeroAmount
policy { actor.is(Accountant) }
}
action Approve on Invoice {
policy { actor.is(Manager) }
}
action Reject on Invoice {
input {
reason: String
}
policy { actor.is(Manager) }
}
action MarkPaid on Invoice {
policy { actor.is(Accountant) }
}
machine InvoiceFlow on Invoice.status {
transition draft -> submitted via Submit
transition submitted -> approved via Approve
transition submitted -> rejected via Reject
transition approved -> paid via MarkPaid
}
}

The status field is marked state, so only the machine InvoiceFlow may change it. Each policy says who may run the action.

3. Check it.

Terminal window
npx appling check models
1 file(s), 0 error(s)

4. Break it on purpose. Change the last transition to approved -> settled and check again:

models/billing.appling:48:5 error "settled" is not a value of InvoiceStatus.
1 file(s), 1 error(s)

The exit code is 1, so the same command guards your CI. Change it back.

5. Ask the model questions.

Terminal window
npx appling simulate reach billing.InvoiceFlow paid models
npx appling simulate actors billing.Approve models
paid is reachable from draft:
Submit -> submitted
Approve -> approved
MarkPaid -> paid
billing.Clerk: no
billing.Director: yes

6. Open the IDE.

Terminal window
npx @appling/ide models

Open http://127.0.0.1:4410. The text sits next to a map of the model, with a search over every element and an inspector that shows who may run each action. The IDE guide covers every panel.

To look first without installing anything, use the demo below. It is the same IDE with the sample sales and CRM model, running entirely in your browser. Click a circle on the canvas to unfold it, or pick an element in the search.

On a small screen, open the demo full screen.

Connect the Appling MCP server to Claude Code

The MCP server gives Claude Code validated access to the model. Claude reads it, asks the simulator, and edits it only through operations that keep the model valid.

1. Install the AI package.

Terminal window
npm install --save-dev @appling/ai

2. Register the server for the model folder.

Terminal window
claude mcp add appling -- npx appling-mcp models

3. Ask about the logic in plain words. Start Claude Code in the project and try:

Which roles may approve an invoice, and from which state?

Claude answers from graph_query and simulate, not from guessing over source files. The server tells it that the model is the source of truth for business rules. The same principle drives our post on building a custom MCP server for Claude Code: a typed tool beats letting the model guess.

4. Change the logic. Ask for a change, such as “let a manager send a rejected invoice back to draft”. Claude uses the edit tool. An edit that would add an error is refused with the reason, and the file stays as it was.

sequenceDiagram
    participant You
    participant CC as Claude Code
    participant MCP as appling-mcp
    participant M as models/*.appling
    You->>CC: "Who may approve an invoice?"
    CC->>MCP: simulate actors billing.Approve
    MCP->>M: read and validate
    MCP-->>CC: Clerk: no, Director: yes
    CC-->>You: Only the Manager role, from submitted

Use the MCP visualization window

Over stdio, the Appling MCP server can open a small visualization window next to your terminal and draw the model there. You keep talking to Claude Code; the window shows what you are talking about.

1. Ask for a view.

Show me the invoice lifecycle.

Claude calls visualize. A frameless window opens at the top right of the screen, above other windows, without taking focus from the terminal. The machine view draws one card per state with what can be done there. Other views draw entities, single actions, use cases and events.

2. Point at something. Click a card or a row to select it, then say “explain this” in the terminal. Claude reads your selection with visual_selection.

3. Review changes in the window. While the window is open, Claude proposes model changes with propose_edits. The window shows the summary and the changed lines. Accept writes them to the files; Reject leaves the files untouched and returns your comment to Claude.

4. Optional: type questions into the window. Questions typed there reach the session through Claude Code channels, a research preview. Start Claude Code with the server named:

Terminal window
claude --dangerously-load-development-channels server:appling

The window needs @appling/ai 0.1.2 or later. It runs on Electron and downloads the binary on first use. On Linux Wayland it runs through XWayland, which GNOME and KDE provide.

Start from the model, not from the code

The takeaway: complex business logic becomes manageable once it has one home that a machine validates, draws and answers questions about. Code, diagrams and AI edits then follow the model instead of each holding their own version of the rules.

Start with one workflow that hurts today, such as approvals or order states, and model only that. The Appling docs cover the full language, a ten minute first model tutorial, deriving a model from an existing codebase with Claude Code, and code generation for Django, Ash, Go and Rust. The source is on GitHub.

If you want help modelling a tangled process in your own system, talk to us.