Skip to Content
ArchitectureServices vs Systems

Services vs Systems (The “Berlin Wall”)

This document defines the hard split between Services and Systems in Gnosis Engine. The goal is to keep logic small, composable, and testable, preventing the emergence of unmaintainable “god files.”

At a high level:

  • Services = Data Protectors. They own writes to the Store and guard data integrity.
  • Systems = Logic Drivers. They own gameplay rules, GnosisRules, and interceptors.

All code must “pick a side.”


Services: The Data Protectors

Mental model: A bank teller. Calm, strict, and predictable.

Who they are

A GnosisService (derived from GnosisServiceBase) that:

  • Owns a namespace in state (under Persistent or Ephemeral).
  • Listens to REQUEST events for its domain.
  • Emits FACT events after committing changes.

What they do

  • Write to the Store: They are the only components allowed to mutate GnosisState for their domain.
  • Validate and Clamp: They enforce invariants and data integrity (e.g., HP cannot be negative).
  • Persist and Serialize: They handle saving/loading for their state.

What they do NOT do

  • No “if the player has Boon X, do Y” logic.
  • No complex balance logic or combo rules.
  • No direct knowledge of GnosisRules or interceptors.

Example (ResourceService)

The ResourceService doesn’t care why you gained 50 gold. It simply receives a REQUEST_RESOURCE_ADD event (already modified by rules), validates the new total, writes it to the Store, and publishes a FACT_RESOURCE_CHANGED event.


Systems: The Logic Drivers

Mental model: A grand strategist. Complex, modular, and aggressive.

Who they are

A System is engine/game logic that:

  • Understands game rules, boons, stats, armor, difficulty, and balance.
  • Lives on top of the Store and Event Bus.
  • Is typically implemented via GnosisRules (Interceptors, Observers, Triggers) or small C# coordinators.

What they do

  • Compute Intent: Deciding what should happen and expressing it as REQUEST events.
  • Intercept and Modify: Altering requests via INTERCEPTOR rules (e.g., doubling damage).
  • React to Facts: Triggering side effects via OBSERVER rules.

What they do NOT do

  • Never write directly into a Service’s private Store region.
  • Never persist or clamp raw values.
  • Should not contain UI code (UI listens to FACTS).

The “Berlin Wall” Test

When you are unsure where a piece of logic belongs, use this table:

QuestionIf YES…If NO…
Does it modify the Store directly?It’s a ServiceIt’s a System
Is it a calculation involving boons/stats/armor?It’s a SystemIt’s a Service
Would this change if I changed game balance?It’s a SystemIt’s a Service
  • If it’s about “shape and safety” of data → Service.
  • If it’s about “what should happen in this game” → System.

Workflow Example: Combat Damage

  1. System (CombatSystem): Computes base damage (10) and publishes REQUEST_DAMAGE.
  2. System (BoonSystem - Interceptor): Sees “Double Damage” trait; modifies request to 20.
  3. System (ArmorSystem - Interceptor): Sees armor on target; modifies request to 15.
  4. Service (HealthService): Receives final request (15), clamps HP, writes to Store, and publishes FACT_PLAYER_DIED.

Key Observation:

  • HealthService knows how to store and clamp health, not how armor works.
  • BoonSystem knows game rules, not where health is stored.
Last updated on