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
Backend
Authentication
External Services
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
Strategy : Passport.js with Auth0
Session Management : koa-session
Supported Providers : Auth0 (production), Local (testing)
APIs : Crossref, Google Sheets
Caching : axios-cache-interceptor
Retry Logic : axios-retry
Application Startup Flow
The application initialization follows this sequence:
Infrastructure Creation
Database connection pool, logger, and Redis client are initialized. const createInfrastructure = (
config : InfrastructureConfig ,
): TE . TaskEither < unknown , CollectedPorts >
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 )
);
Read Models Replay
All events are replayed through read models to build current state. const { dispatchToAllReadModels , queries } = dispatcher ( logger );
dispatchToAllReadModels ( events );
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
Write Operations (Commands)
Command Reception
HTTP request is routed to a command handler
Validation
Input is validated using io-ts codecs
Business Logic
Resource action determines which events to create
Event Persistence
Events are persisted to PostgreSQL
State Update
Events are dispatched to all read models
Read Operations (Queries)
Query Reception
HTTP request is routed to a query handler
Read Model Access
Query accesses the appropriate read model
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