Skip to main content

System Architecture

Sciety is built using event sourcing and CQRS (Command Query Responsibility Segregation) patterns. The application is powered by a Node.js server using Koa, with PostgreSQL for event storage and Redis for caching.

Core Components

Event Store

Immutable log of all domain events stored in PostgreSQL

Write Side

Commands that create new domain events

Read Models

Projections built from events for efficient querying

HTTP Server

Koa-based web server with authentication and routing

Technology Stack

  • Runtime: Node.js 24
  • Web Framework: Koa 2.15
  • Type System: TypeScript with io-ts for runtime validation
  • Functional Programming: fp-ts for functional patterns
  • Database: PostgreSQL (via pg driver)
  • Cache: Redis

Application Startup Flow

The application initialization follows this sequence:
1

Infrastructure Creation

Database connection pool, logger, and Redis client are initialized.
const createInfrastructure = (
  config: InfrastructureConfig,
): TE.TaskEither<unknown, CollectedPorts>
2

Event Store Initialization

Events table is created (if needed) and all events are loaded from PostgreSQL.
CREATE TABLE IF NOT EXISTS events (
  id uuid,
  type varchar,
  date timestamp,
  payload jsonb,
  PRIMARY KEY (id)
);
3

Read Models Replay

All events are replayed through read models to build current state.
const { dispatchToAllReadModels, queries } = dispatcher(logger);
dispatchToAllReadModels(events);
4

Server Start

Koa server is created with routes and starts listening on port 80.
const server = createTerminus(
  createApplicationServer(router, adapters, config),
  terminusOptions(logger)
);
server.listen(80);

Request Flow

1

Command Reception

HTTP request is routed to a command handler
2

Validation

Input is validated using io-ts codecs
3

Business Logic

Resource action determines which events to create
4

Event Persistence

Events are persisted to PostgreSQL
5

State Update

Events are dispatched to all read models
1

Query Reception

HTTP request is routed to a query handler
2

Read Model Access

Query accesses the appropriate read model
3

Response

Data is returned directly from in-memory state

Key Design Principles

Immutable Events: All state changes are captured as immutable domain events
Event Replay: System state can be reconstructed by replaying all events
Separation of Concerns: Write side (commands) and read side (queries) are completely separated
Functional Programming: Heavy use of fp-ts for type-safe functional patterns (Either, Task, TaskEither)

Directory Structure

src/
├── domain-events/        # Domain event definitions
├── event-store/          # Event store interface
├── write-side/           # Commands and write models
│   ├── commands/         # Command handlers
│   └── resources/        # Domain resources
├── read-models/          # Query-side projections
│   ├── lists/
│   ├── evaluations/
│   ├── users/
│   ├── groups/
│   └── ...
├── infrastructure/       # Database, logging, external adapters
├── http/                 # Koa server and routes
└── index.ts             # Application entry point

Next Steps

Event Sourcing

Learn how events are the source of truth

Read/Write Models

Understand CQRS implementation

Build docs developers (and LLMs) love