jonnify·codemojex /codemojex/overview/the-architecture-at-a-glance

Codemojex · dive C0.3

The architecture — four layers, one glance.

Codemojex is four layers, and the layering is the architecture. Requests enter at a thin Phoenix surface behind privacy-safe views; the Codemojex facade delegates into the domain systems; durable work travels on the EchoMQ bus over Valkey, on lanes named by branded ids; and state rests on a three-tier storage floor — Postgres the system of record, Valkey the derived real-time state, EchoStore the immutable near-cache. Money and the secret live on the floor. One glance holds the whole game.

Hover or select a layer. The readout names what the layer owns and the real modules that own it.

1 · surface CodemojexWeb JSON API · Channels privacy-safe views 2 · facade Codemojex Guesses · Rooms · Wallet ScoreWorker · Settle 3 · bus EchoMQ.Lanes "cm" — lane PLR "cm-settle" — lane GAM 4 · floor Postgres — record Valkey — real-time EchoStore — near-cache
Four layers, each owning its tier; the teal band is the floor — money and the secret rest there, and the surface never selects them. Source: lib/codemojex/game.ex and the design §The architecture at a glance.

The surface — codemojex_web/*: the JSON API, the room channel, and the LiveView lobby, all reading through Codemojex.View — no secret, no others' guesses. The surface holds no game state and never scores.

§1

One sentence holds the game

game.ex · the facade moduledoc

The facade module's own documentation is the architecture in one sentence. Read it once as prose, then once as a map — every clause is a layer of the stack above.

Codemojex on the bus: a six-emoji code-breaking competition whose entities are branded components persisted in Postgres, whose guesses are jobs on per-player lanes scored by a single authority, whose three currencies mutate atomically in the database through a wallet with a transaction ledger, whose rooms template their games, and whose diamond prize pools settle through a second queue (winner-take-all for a classic game, a sealed top-K split for a golden game).
The Codemojex moduledoc, lib/codemojex/game.ex — quoted verbatim.

“Branded components persisted in Postgres” is the floor. “Jobs on per-player lanes scored by a single authority” is the bus and its consumers. “A wallet with a transaction ledger” is a domain system behind the facade. And the moduledoc closes where the page ends: “Startup is Codemojex.Application's job — the Repo, PubSub, the EchoMQ bus and consumers, and the Phoenix endpoint come up there.”

§2

Layer by layer

surface → facade → bus → floor

Layer 1 — the surface. codemojex_web/*: the JSON API, the room channel, and the LiveView lobby. Every read goes through Codemojex.View, whose moduledoc opens with the invariant: “nothing here returns the secret, and nothing returns another player's guesses.” The facade marks its view delegates the same way — “views (privacy-preserving: no secret, no others' guesses).”

Layer 2 — the facade and the systems. The Codemojex module is a thin facade delegating into small domain systems, each owning one concern: Codemojex.Guesses (the play API), Codemojex.Rooms (templates and lifecycle), Codemojex.Wallet (the currencies, atomically in Postgres), Codemojex.Scoring (the pure linear engine), Codemojex.Board (the competitive state), Codemojex.Settle (the payout job). Codemojex.Guesses.submit/3 validates a six-emoji guess against the game's keyboard, overlays the player's locks (Codemojex.Locks.merge/3), charges the room's currency through the wallet — and then hands the guess off. The host never scores; the consumer does.

Layer 3 — the bus. submit/3 mints a branded JOB (EchoData.BrandedId.generate!("JOB")) and enqueues it — Lanes.enqueue(Bus.conn(), @queue, player, job, payload), queue "cm". The lane is named by the player's PLR, “so the bus rotates service across players and one keyboard masher cannot starve the field.” Codemojex.ScoreWorker is the single scoring authority: EchoMQ.Consumer drains the queue through Lanes.claim, the player id arriving as the lane group. Settlement is a second queue, "cm-settle", whose lane is the game's GAM — “the move-then-settle split is the Exchange pattern: the guess queue competes, the settle queue pays” (Codemojex.Settle). Two more lanes complete the picture: notifications on "cm.notify" and inbound bot commands, each keyed by chat and drained by its own consumer.

Layer 4 — the floor. Three tiers, each holding the data that fits it. Postgres is the system of record — Codemojex.Store.game/1 reads it, and players, transactions, rooms, games (secret included, server-side), emoji sets, and guesses rest there. Valkey holds the derived real-time state — the per-game leaderboard sorted set Codemojex.Board writes, the attempt counters, and the bus itself. EchoStore is the immutable near-cache — Codemojex.Cache.fetch_game/1 over the tables Codemojex.Tables declares (:cm_games, :cm_emojisets), an L1 :ets hit in the caller's process with the shared Valkey as L2. A game's secret is immutable for the game's life, so coherence is :none and the cache never goes stale.

The tree stands the floor up first. Codemojex.Application starts the layers in dependency order: “the relational system of record (Repo) and PubSub first; then the EchoMQ bus (Bus, the shared Valkey connector); then the EchoStore near-cache tier (Tables …); then the consumers — the scoring authority, the settlement worker, the notification worker, and the bot-command worker …; then the Phoenix endpoint.”

Grounding — game.ex (the facade, Guesses, ScoreWorker, Settle) · store.ex (Store, Bus, Cache) · board.ex · view.ex · application.ex · the design §The architecture at a glance.

§3

A guess's journey

submit → scored

One entity crossing all four layers, top to floor and back. Step the six stations: a guess leaves the facade as a JOB on the player's lane, is claimed by the single scoring authority, touches every storage tier, and returns to the surface as a scored event.

1 · submit submit/3 layer 2 2 · enqueue JOB · lane PLR layer 3 3 · claim Lanes.claim layer 3 4 · score score → GES layer 4 5 · record Board.record layer 4 6 · announce scored event layer 1
The layer tags under the stations trace the descent and return: facade → bus → floor → surface. For a golden game the sixth hop is suppressed — the score is sealed until reveal (the blind contract). Source: Codemojex.Guesses and Codemojex.ScoreWorker, lib/codemojex/game.ex.

Submit — Codemojex.Guesses.submit(game, player, emojis) reads the game's mutable state from the system of record (Store.game/1), validates the six emoji against the keyboard, overlays locks (Locks.merge/3), and charges the wallet (Wallet.charge_guess/3). Layer 2 work — the host never scores.

The path is asymmetric on purpose. The submit side runs on the caller and touches only the system of record and the wallet; the scoring side runs on the consumer and is the only reader of the secret — “the game's mutable state is read from the system of record; the cache is trusted only for the immutable secret on the scoring path” (submit/3's doc). A re-delivered guess re-scores to the same number, because the engine is pure; a guess for an unknown game answers :ok — a drop, never a retry loop.

Acceptance — “a guess submitted on the lane is scored and reaches the leaderboard”: stories/rooms-and-games.stories.md, generated from the as-built BDD suite.

§4

Money and the secret

the floor · privacy.stories.md

The floor is where the two sensitive things rest, and the layers above are shaped so neither travels. The secret is a Postgres column read through the near-cache on the scoring path only; no view returns it — “the game view never carries the secret” is a generated acceptance scenario, not a promise. Money is relational: the three currencies “mutate atomically in the database through a wallet with a transaction ledger,” and the diamond prize pools settle through the second queue, not through the surface.

What crosses the layers is identities and messages about them — a GAM, a PLR, a JOB, a GES, and a scored event carrying a name and a percentage. That is the branded-component law applied: the boundaries are real, and the same code runs whether the systems share a node or not.

Acceptance — stories/privacy.stories.md: “the game view never carries the secret” · “a player's history shows only their own attempts” · “the leaderboard exposes scores, never guess content.”

§5

References

sources · related

Sources

Related