Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 10: Architecture Decision Records

Documenting Key Technical Decisions

This chapter documents the major architectural decisions for the POS Platform using Architecture Decision Records (ADRs). Each ADR captures the context, decision, and consequences of a significant technical choice.


What is an ADR?

Architecture Decision Records provide a structured way to document important technical decisions:

ADR Structure
=============

+------------------------------------------------------------------+
|  ADR-XXX: [Title]                                                 |
+------------------------------------------------------------------+
|  Status: [proposed | accepted | deprecated | superseded]         |
|  Date: YYYY-MM-DD                                                 |
|  Deciders: [who made the decision]                               |
+------------------------------------------------------------------+
|                                                                   |
|  CONTEXT                                                          |
|  - What is the issue?                                            |
|  - What forces are at play?                                      |
|  - What constraints exist?                                       |
|                                                                   |
|  DECISION                                                         |
|  - What is the change?                                           |
|  - What did we choose?                                           |
|                                                                   |
|  CONSEQUENCES                                                     |
|  - What are the positive outcomes?                               |
|  - What are the negative outcomes?                               |
|  - What risks are introduced?                                    |
|                                                                   |
+------------------------------------------------------------------+

ADR-001: Schema-Per-Tenant Multi-Tenancy

+==================================================================+
|  ADR-001: Schema-Per-Tenant Multi-Tenancy                        |
+==================================================================+
|  Status: ACCEPTED                                                 |
|  Date: 2025-12-29                                                |
|  Deciders: Architecture Team                                      |
+==================================================================+

CONTEXT
-------
We are building a multi-tenant POS platform that will serve multiple
independent retail businesses. Each tenant needs:

1. Strong data isolation for security and compliance
2. Easy backup and restore of individual tenant data
3. Ability to scale individual tenants independently
4. Simple data model without tenant_id on every table
5. Compliance with SOC 2 and potential HIPAA requirements

We evaluated three multi-tenancy strategies:

  Strategy A: Shared Tables (Row-Level)
  - All tenants share tables
  - tenant_id column on every table
  - WHERE tenant_id = ? on every query

  Strategy B: Separate Databases
  - Each tenant gets own database
  - Complete isolation
  - High connection overhead

  Strategy C: Schema-Per-Tenant
  - Single database, separate schemas
  - SET search_path per request
  - Logical isolation, shared infrastructure

DECISION
--------
We will use SCHEMA-PER-TENANT multi-tenancy (Strategy C).

Each tenant gets a dedicated PostgreSQL schema:
  - shared schema: Platform-wide data (tenants, plans, features)
  - tenant_xxx schema: All tenant-specific tables

The tenant is resolved from the subdomain (e.g., nexus.pos-platform.com)
and the database search_path is set accordingly.

CONSEQUENCES
------------
Positive:
  + Strong logical isolation between tenants
  + No tenant_id needed on every table (cleaner data model)
  + Easy per-tenant backup: pg_dump -n tenant_xxx
  + Easy per-tenant restore without affecting other tenants
  + Single connection pool serves all tenants
  + Simpler queries (no WHERE tenant_id = ?)
  + Compliance-friendly for audits and data requests

Negative:
  - Migrations must be applied to all tenant schemas
  - Cross-tenant queries require explicit schema references
  - PostgreSQL has soft limit (~10,000 schemas per database)
  - Slight complexity in tenant provisioning

Risks:
  - Must ensure search_path is ALWAYS set correctly
  - Schema migration failures could leave tenants inconsistent
  - Need robust tenant provisioning automation

Mitigations:
  - Middleware validates and sets search_path on every request
  - Migration runner applies changes atomically per tenant
  - Tenant provisioning is scripted and tested

ADR-002: Offline-First POS Architecture

+==================================================================+
|  ADR-002: Offline-First POS Architecture                         |
+==================================================================+
|  Status: ACCEPTED                                                 |
|  Date: 2025-12-29                                                |
|  Deciders: Architecture Team                                      |
+==================================================================+

CONTEXT
-------
POS terminals operate in retail environments where network
connectivity is unreliable:

1. Internet outages occur (ISP issues, weather, accidents)
2. WiFi can be congested during peak shopping hours
3. Store networks may have maintenance windows
4. Rural locations may have poor connectivity

A traditional online-required POS would:
- Block sales during outages (lost revenue)
- Show errors during slow connections (poor UX)
- Require manual workarounds (paper receipts)

Business requirements:
- Sales must NEVER be blocked by network issues
- Receipts must print immediately
- Data must eventually sync to central system
- Inventory should be reasonably accurate

DECISION
--------
We will implement OFFLINE-FIRST architecture for POS clients.

Key design elements:
1. Local SQLite database on each POS terminal
2. All operations work against local database first
3. Event queue for pending changes
4. Background sync when connectivity available
5. Conflict resolution for concurrent changes

Data flow:
  User Action -> Local DB -> Event Queue -> [Background] -> Central API

CONSEQUENCES
------------
Positive:
  + Sales never blocked by network issues
  + Instant response time (local operations)
  + Resilient to any connectivity problem
  + Business continues regardless of server status
  + Better user experience for cashiers

Negative:
  - Data is eventually consistent (not immediate)
  - Inventory counts may drift until sync
  - More complex architecture
  - Conflict resolution logic required
  - Local storage management needed

Risks:
  - Data loss if local device fails before sync
  - Inventory overselling possible during outages
  - Conflict resolution edge cases

Mitigations:
  - Aggressive sync when online (every 30 seconds)
  - Local database backup to secondary storage
  - Conservative inventory thresholds
  - Clear offline indicator in UI
  - Deterministic conflict resolution rules

ADR-003: Event Sourcing for Sales Domain

+==================================================================+
|  ADR-003: Event Sourcing for Sales Domain                        |
+==================================================================+
|  Status: ACCEPTED                                                 |
|  Date: 2025-12-29                                                |
|  Deciders: Architecture Team                                      |
+==================================================================+

CONTEXT
-------
The Sales domain has specific requirements that traditional CRUD
does not adequately address:

1. Complete audit trail required (PCI-DSS compliance)
2. Need to answer "what happened?" not just "what is?"
3. Offline clients need conflict-free merge capability
4. Historical analysis (sales trends, patterns)
5. Debugging production issues by replaying events

Traditional CRUD limitations:
- Only stores current state
- Updates overwrite history
- Hard to reconstruct past states
- Audit logs separate from data model

DECISION
--------
We will use EVENT SOURCING for the Sales aggregate.

Implementation:
1. Append-only event store in PostgreSQL
2. Events are the source of truth
3. Read models (projections) for queries
4. Snapshots for performance on long streams

Events captured:
- SaleCreated, SaleLineItemAdded, PaymentReceived, SaleCompleted
- SaleVoided, RefundProcessed
- All inventory changes (InventorySold, InventoryAdjusted)

NOT event-sourced (traditional CRUD):
- Products (read-heavy, infrequent changes)
- Employees (HR data, simple lifecycle)
- Locations (configuration data)

CONSEQUENCES
------------
Positive:
  + Complete audit trail built into data model
  + Temporal queries ("inventory on Dec 15 at 3pm")
  + Offline sync via event merge (append-only = no conflicts)
  + Debugging by event replay
  + Analytics on event streams
  + Natural fit for CQRS pattern

Negative:
  - More complex than CRUD
  - Requires event versioning strategy
  - Projections must be rebuilt if logic changes
  - Storage grows over time (mitigated by snapshots)
  - Learning curve for developers

Risks:
  - Event schema evolution complexity
  - Projection bugs cause stale read models
  - Performance without proper snapshotting

Mitigations:
  - Event versioning from day one
  - Automated projection rebuild process
  - Snapshot every 100 events
  - Clear documentation and training

ADR-004: JWT + PIN Authentication

+==================================================================+
|  ADR-004: JWT + PIN Authentication                               |
+==================================================================+
|  Status: ACCEPTED                                                 |
|  Date: 2025-12-29                                                |
|  Deciders: Architecture Team, Security Team                       |
+==================================================================+

CONTEXT
-------
POS systems have unique authentication requirements:

1. API access needs secure, stateless authentication
2. Cashiers need quick clock-in at physical terminals
3. Sensitive actions need additional verification
4. Multiple employees may share a terminal
5. Terminals may be offline

Requirements:
- Strong authentication for API/Admin access
- Fast authentication for cashiers (< 2 seconds)
- Manager override capability
- Works offline for cashier PIN

Industry standards:
- JWT is standard for API authentication
- PINs are standard for POS quick access
- Password + MFA for admin portal access

DECISION
--------
We will implement a HYBRID authentication system:

1. JWT for API Authentication
   - Admin portal uses email + password + optional MFA
   - Issues JWT token (15 min access, 7 day refresh)
   - Standard Bearer token in Authorization header

2. PIN for POS Terminal Access
   - 4-6 digit PIN per employee
   - Stored as bcrypt hash in database
   - Used for: clock-in, sale attribution, drawer access

3. Manager Override
   - Sensitive actions require manager PIN
   - Void, large discount, price override
   - Manager enters their PIN to authorize

4. Offline PIN Validation
   - Employee records with PIN hashes cached locally
   - Validated against local cache when offline
   - Sync employee changes when online

CONSEQUENCES
------------
Positive:
  + Secure API access with industry-standard JWT
  + Fast cashier workflow with PIN
  + Manager oversight on sensitive operations
  + Works offline for POS operations
  + Clear audit trail (who did what)

Negative:
  - Two authentication systems to maintain
  - PIN is less secure than password (brute force)
  - Local PIN cache could be extracted
  - Token refresh complexity

Risks:
  - PIN guessing attacks
  - Stolen JWT tokens
  - Stale employee cache (terminated employee)

Mitigations:
  - Rate limiting on PIN attempts (3 failures = lockout)
  - Short JWT expiry (15 minutes)
  - Aggressive employee sync (every 5 minutes)
  - PIN attempt logging and alerting
  - Secure local storage encryption

ADR-005: PostgreSQL as Primary Database

+==================================================================+
|  ADR-005: PostgreSQL as Primary Database                         |
+==================================================================+
|  Status: ACCEPTED                                                 |
|  Date: 2025-12-29                                                |
|  Deciders: Architecture Team                                      |
+==================================================================+

CONTEXT
-------
We need a database that supports:

1. Schema-per-tenant multi-tenancy
2. JSONB for flexible event storage
3. Strong ACID guarantees for financial data
4. Good performance at scale
5. Mature ecosystem and tooling

Options considered:
- PostgreSQL: Schema support, JSONB, mature
- MySQL: Popular, but weaker schema support
- SQL Server: Good, but licensing costs
- MongoDB: Document store, no ACID, no schemas
- CockroachDB: Distributed, but complexity

DECISION
--------
We will use POSTGRESQL 16 as the primary database.

Justifications:
1. Native schema support for multi-tenancy
2. Excellent JSONB for event storage
3. Strong ACID for financial transactions
4. Proven at scale (Instagram, Uber, etc.)
5. Rich extension ecosystem (PostGIS, etc.)
6. Open source, no licensing costs
7. Excellent tooling (pgAdmin, pg_dump)

CONSEQUENCES
------------
Positive:
  + Perfect fit for schema-per-tenant
  + JSONB enables flexible event data
  + Strong consistency guarantees
  + Mature, well-documented
  + No licensing costs
  + Excellent community support

Negative:
  - Single point of failure without replication
  - Requires PostgreSQL expertise
  - Not as horizontally scalable as NoSQL
  - Schema migrations need coordination

Mitigations:
  - Streaming replication for HA
  - Regular backups with pg_dump
  - Team training on PostgreSQL
  - Migration automation tooling

ADR-006: ASP.NET Core for Central API

+==================================================================+
|  ADR-006: ASP.NET Core for Central API                           |
+==================================================================+
|  Status: ACCEPTED                                                 |
|  Date: 2025-12-29                                                |
|  Deciders: Architecture Team                                      |
+==================================================================+

CONTEXT
-------
We need a backend framework that supports:

1. High-performance API serving
2. Strong typing for complex domain
3. Entity Framework for database access
4. SignalR for real-time features
5. Docker deployment
6. Team expertise alignment

Options considered:
- ASP.NET Core (C#): Performance, typing, EF Core
- Node.js (Express): Fast dev, but weak typing
- Go (Gin): Performance, but less ecosystem
- Python (FastAPI): ML integration, but slower
- Java (Spring): Enterprise, but verbose

Team context:
- Existing .NET experience from Bridge project
- C# used for MAUI mobile app
- Entity Framework expertise available

DECISION
--------
We will use ASP.NET CORE 8.0 for the Central API.

Justifications:
1. Exceptional performance (near Go levels)
2. Strong typing catches bugs at compile time
3. Entity Framework Core for PostgreSQL
4. Built-in SignalR for real-time
5. Excellent Docker support
6. Team already proficient in C#
7. Same language as POS client and mobile app

CONSEQUENCES
------------
Positive:
  + High performance for API workloads
  + Strong typing reduces runtime errors
  + Seamless EF Core integration
  + Built-in dependency injection
  + Excellent tooling (Visual Studio, Rider)
  + C# across entire stack (API, Client, Mobile)

Negative:
  - Larger runtime than Go or Rust
  - Windows-centric tooling (though Linux deployment)
  - C# developers cost more than Node.js

Mitigations:
  - Alpine-based Docker images minimize size
  - Use VS Code or Rider on Mac/Linux
  - Leverage existing team expertise

ADR Index

ADRTitleStatusDate
ADR-001Schema-Per-Tenant Multi-TenancyAccepted2025-12-29
ADR-002Offline-First POS ArchitectureAccepted2025-12-29
ADR-003Event Sourcing for Sales DomainAccepted2025-12-29
ADR-004JWT + PIN AuthenticationAccepted2025-12-29
ADR-005PostgreSQL as Primary DatabaseAccepted2025-12-29
ADR-006ASP.NET Core for Central APIAccepted2025-12-29

Future ADRs (Planned)

ADRTitleStatus
ADR-007React for Admin PortalProposed
ADR-008Electron vs Tauri for POS ClientProposed
ADR-009Redis for Session & CacheProposed
ADR-010Shopify Sync StrategyProposed
ADR-011Payment Gateway IntegrationProposed
ADR-012Logging and Monitoring StackProposed

How to Propose a New ADR

ADR Proposal Process
====================

1. Copy the ADR template
2. Fill in Context, Decision, Consequences
3. Set Status to "proposed"
4. Submit for architecture review
5. Discuss in architecture meeting
6. Update based on feedback
7. Set Status to "accepted" when approved
8. Add to ADR Index

ADR Template

# ADR-XXX: [Title]

**Status**: proposed | accepted | deprecated | superseded
**Date**: YYYY-MM-DD
**Deciders**: [Names or roles]

## Context

[What is the issue? What forces are at play?]

## Decision

[What is the change? What did we choose?]

## Consequences

### Positive
- [Benefit 1]
- [Benefit 2]

### Negative
- [Drawback 1]
- [Drawback 2]

### Risks
- [Risk 1]
- [Risk 2]

### Mitigations
- [Mitigation 1]
- [Mitigation 2]

Summary

These Architecture Decision Records capture the foundational technical decisions for the POS Platform:

ADRKey DecisionPrimary Benefit
ADR-001Schema-per-tenantStrong isolation without complexity
ADR-002Offline-firstSales never blocked by network
ADR-003Event sourcingComplete audit trail and temporal queries
ADR-004JWT + PINSecure API + fast cashier workflow
ADR-005PostgreSQLSchema support and JSONB flexibility
ADR-006ASP.NET CorePerformance and unified C# stack

These decisions form the architectural foundation upon which the rest of the system is built.


End of Part II: Architecture

Next: Part III: Database - Chapter 11: Database Strategy