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
| ADR | Title | Status | Date |
|---|---|---|---|
| ADR-001 | Schema-Per-Tenant Multi-Tenancy | Accepted | 2025-12-29 |
| ADR-002 | Offline-First POS Architecture | Accepted | 2025-12-29 |
| ADR-003 | Event Sourcing for Sales Domain | Accepted | 2025-12-29 |
| ADR-004 | JWT + PIN Authentication | Accepted | 2025-12-29 |
| ADR-005 | PostgreSQL as Primary Database | Accepted | 2025-12-29 |
| ADR-006 | ASP.NET Core for Central API | Accepted | 2025-12-29 |
Future ADRs (Planned)
| ADR | Title | Status |
|---|---|---|
| ADR-007 | React for Admin Portal | Proposed |
| ADR-008 | Electron vs Tauri for POS Client | Proposed |
| ADR-009 | Redis for Session & Cache | Proposed |
| ADR-010 | Shopify Sync Strategy | Proposed |
| ADR-011 | Payment Gateway Integration | Proposed |
| ADR-012 | Logging and Monitoring Stack | Proposed |
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:
| ADR | Key Decision | Primary Benefit |
|---|---|---|
| ADR-001 | Schema-per-tenant | Strong isolation without complexity |
| ADR-002 | Offline-first | Sales never blocked by network |
| ADR-003 | Event sourcing | Complete audit trail and temporal queries |
| ADR-004 | JWT + PIN | Secure API + fast cashier workflow |
| ADR-005 | PostgreSQL | Schema support and JSONB flexibility |
| ADR-006 | ASP.NET Core | Performance and unified C# stack |
These decisions form the architectural foundation upon which the rest of the system is built.
End of Part II: Architecture