Mohamed Melki.

Case study 01  ·  Freelance, solo  ·  2026  ·  Architecture, backend, frontend, infrastructure

Carini

A marketplace of care plans for the Tunisian diaspora — and proof, every single visit, that it actually happened.

Backend
Java 21 · Quarkus · Gradle multi-module · PostgreSQL + Flyway · Keycloak
Frontend
Vue 3 · Vite · Tailwind v4 · Reka UI · Pinia · TanStack Query · vue-i18n
State
104 tests, 0 failures. Payment collection mocked behind a port; everything downstream of it is real.
Carini's landing page: “Your parents are there. You are here.” with a city and needs selector beneath it.
The funnel works without an account: what we do → four questions → ranked offers → the order.

01

What it is

Someone working in Paris, Montreal or Dubai buys a plan for a parent who stayed in Tunisia. Carini dispatches real nurses, laboratories and clinics against that plan, keeps the family informed in language they actually use, and pays each partner for the work they delivered.

Five roles use the same system and see completely different things: the buyer abroad, the beneficiary at home, the partner doing the visit, the consultant composing offers, and support — who needs the literal truth when a worried family calls.

Nothing about diabetes, or nursing, or groceries is written into the code. A plan is a template — PlanDefinition-shaped, versioned, frozen once published — and the engine underneath knows only about entitlements, schedules and state.

02

The constraint that shaped everything

The person paying is not the person whose medical record it is. Tunisian law puts the beneficiary in charge of their own health data, so consent could not be a checkbox bolted on at the end — it had to be an object with a scope, a grantee and a revocation.

Which produces the sequence below. A son pays for his mother's plan, arranges her care, and cannot read a word of it until she agrees. When she revokes, the record goes dark again while he remains the buyer.

GET  /api/care-plans/{id}             buyer, no consent      → 404   never 403: "there is a plan here" leaks
POST /api/care-plans/{id}/checkout    same buyer             → 200   paying is not reading
POST /api/consents                    consultant records written evidence
GET  /api/care-plans/{id}             buyer, with consent    → 200

Three gates, and the difference between them is the product: role asks what job you do, ownership asks whether this is your order, consent asks whether the beneficiary agreed. All three are checked in the service layer — never in a controller, and never in a UI guard, which is a convenience rather than a control.

Staff read by role instead, and every such read writes CARE_PLAN_VIEWED to the audit trail. The proof-of-delivery code is returned by no endpoint at all: a partner who could fetch it could confirm a visit they never made.

03

Boundaries that do not compile when crossed

Carini is a modular monolith, and the modularity is structural rather than advisory. A module publishes an api — value objects, enums, ports — and its implementation is never exported, so importing another module's internals does not compile. There is no meeting to have about it.

domain/            pure model — Money, Market, Recurrence. No framework.
platform/audit/    hash-chained audit trail used by every module
platform/outbox/   events publish only if their transaction committed
modules/<ctx>/api  contracts: value objects, enums, ports
modules/<ctx>/impl entities and services — never exported
adapters/          the only place a third-party SDK may appear
app/               the deployable: wiring, Flyway, boundary tests

Tests over the dependency graph cover what the build graph cannot see: layering direction, framework leakage into the domain, and the direction adapters are allowed to depend in. The pure domain is enforced, not hoped for.

04

The money loop, closed

An integration test follows one dinar from the buyer's card to the partner's bank account. Cash arriving is not revenue — it is an obligation to deliver care, drawn down one visit at a time.

buyer pays 180.000 TND      DEBIT  CASH:PSP                     CREDIT PLAN_DEFERRED_REVENUE:{plan}
lab delivers, verified      DEBIT  PLAN_DEFERRED_REVENUE:{plan} CREDIT PARTNER_PAYABLE:{lab}  40.800
                                                                CREDIT COMMISSION_REVENUE:TN   7.200
payout run                  DEBIT  PARTNER_PAYABLE:{lab}        CREDIT CASH:PAYOUT            40.800

The ledger is append-only double entry, and its balance is enforced by a deferred check in the database rather than by application code. A partner's balance is always derived from the ledger and never stored, because a stored balance is a second source of truth waiting to disagree with the first.

RuleEnforced by
A delivered visit is paid for once, ever a unique index on the payout line's service request — not a code review
gross = commission + net, to the millime a database check constraint and a currency-aware Money; the partner absorbs no rounding
An unpriced delivery is never guessed at accrual skips it and writes SERVICE_UNPRICED to the audit trail
A partner with no bank details is refused loudly the payout run reports it instead of skipping it silently
Paying twice charges once a unique idempotency key on the payment

05

Work that cannot get stuck

A plan bought in Paris has to become a nurse at a door in Tunis, repeatedly, for a year. Everything between those two facts is machinery whose failure mode must be a retry, never a silence.

CapabilityGuarantee
Materialisation a plan definition becomes entitlements, pinned to the exact version that was sold
Recurrence month-end never drifts, daylight saving is handled, and a recurrence rule the engine cannot honour is rejected at authoring time rather than at three in the morning
Scheduler SKIP LOCKED; the claim and the work are raised in one transaction, so a failure retries instead of skipping a quarter
State machine ten states, every transition tested — including the proof that nothing can get stuck
SLA escalation breaches reach support while there is still time to fix them by telephone
Proof of delivery a six-digit code the beneficiary reads aloud; a rejected code does not move the request
Quota rolls by period, and is refunded when a visit is cancelled
Outbox at-least-once with backoff and a dead-letter queue — never “commit, then publish”

Everything that raises work is idempotent on an origin key, because schedulers tick twice and events get redelivered — and a beneficiary must not receive two nurses on account of our retry policy.

06

One state machine, several vocabularies

Every unit of work runs the same lifecycle. What changes is what the states are called, which comes from the kind of service in the catalogue — so a delivery rider and a home nurse move through identical code and read entirely different words.

IN_PROGRESS on a…the family reads
home nursing visit“the nurse is on the way”
grocery delivery“out for delivery”
hotel stay“your stay has started”

Support keeps the literal statuses and the full actor-by-actor trail, because “performed, but not yet verified” is exactly what they need when the phone rings. The family gets four milestones in plain language. Same data, two audiences, one source.

07

Arabic is not a translation pass

The interface is French, English and Arabic, and switching to Arabic mirrors the entire layout — navigation, tables, progress tracks. That works because the layout is written with CSS logical properties throughout, and because language and direction are derived from one choice in one place, rather than being two settings that can drift apart.

A right-to-left mode assembled from a second stylesheet is a mode that half-applies. This one has nowhere to half-apply.

08

What is not built

A portfolio that only lists what works is a sales page. This is where the project actually stands.

Not builtWhy
Real payment collection needs a merchant contract — the largest business risk, and not an engineering one. The port and every ledger posting behind it are finished; the mock logs a warning on every call and stamps its references, so a mock that ever reached production would announce itself
WhatsApp notifications needs Business API credentials
Conditional triggers materialised but dormant; they need clinical observation ingestion first
Renewals and recurring billing only the initial charge exists

09

What I would tell you over coffee

The interesting decisions in Carini were all about where a rule lives, not what the rule is. Consent in the service layer, balance in the database, direction in the locale, module boundaries in the build. Each one moves a class of mistake from “possible if someone forgets” to “impossible without noticing”.

The rest is discipline: never double for money, never commit-then-publish, never a status that can quietly stay where it is. None of it is clever. All of it is the reason a feature can be added next year without holding your breath.

Next case study DocDock A clinic with no application server