ARCHITECTURE & DESIGN DECISIONS
Phase 1 — Why a modular monolith, and how it’s built
The architecture was not designed upfront — it emerged. The database evolved as each module was built. The framework choices were made for pragmatic reasons: speed of development, component libraries that eliminate GUI work, and the ability to run on a factory floor from a single Mac Mini. This document records what was chosen, why, and what the trade-offs are.
MONOLITH
+ React
→ Postgres
API Routes
TABLE OF CONTENTS
1. THE ARCHITECTURE: MODULAR MONOLITH
The system is a Modular Monolith — a single application with clearly separated internal modules, sharing a single database. Not microservices. Not a traditional monolith. The sweet spot in between.
Traditional Monolith
Single codebase, no module separation. Everything tangled. Not this.
Modular Monolith ← THIS
Single application, clear module boundaries, shared database. Right-sized for a factory with 10–12 machines and limited concurrent users.
Microservices
Independent services with separate databases. Overkill for this scale. Adds network latency, distributed transaction complexity, and operational overhead.
Why Not Microservices?
- Scale doesn’t demand it. 10–12 machines, dozens of users, hundreds (not millions) of daily transactions. A single process handles this trivially.
- ACID transactions across modules. When an order is created, materials are calculated, work cards are generated, and stock is allocated — all in a single database transaction. With microservices, this becomes a distributed saga nightmare.
- One developer. Microservices require a team to manage deployment, monitoring, service mesh, and inter-service communication. A solo developer needs simplicity.
- Factory network, not cloud. This runs on a Mac Mini on the factory floor, not a Kubernetes cluster. One process, one database, one deploy target.
The trade-off is known: When the database fails, everything fails. All modules share one SQLite/PostgreSQL instance — there is a single point of failure. For a factory ERP with on-site hardware, this is an acceptable trade-off. The alternative (distributed databases, eventual consistency) adds complexity that would slow development without meaningful benefit at this scale.
Module Boundaries
solen_backend/ ├── app/ │ ├── api/ # REST API layer │ │ ├── auth/ # Authentication & authorization │ │ ├── materials/ # Raw material management │ │ ├── suppliers/ # Supplier management │ │ ├── siparis/ # Order management │ │ ├── production/ # Production operations │ │ ├── teknik/ # Cable design & machines │ │ ├── stock/ # Stock & projection │ │ ├── ai/ # AI chat & search │ │ └── ... # 15+ more route groups │ ├── models/ # SQLAlchemy data models │ │ ├── teknik/ # machines.py, cable_design.py, standards.py │ │ ├── siparis/ # order.py, customer.py │ │ ├── production/ # production_session.py, work_card.py │ │ └── ... # material.py, supplier.py, user.py │ ├── schemas/ # Pydantic validation schemas │ ├── services/ # Business logic layer │ │ ├── production_calculator.py │ │ ├── work_card_generator.py │ │ └── projeksiyon_service.py │ ├── core/ # Config, database, shared utilities │ └── utils/ # Production constants, helpers ├── migrations/ # Database migration scripts └── main.py # FastAPI application entry point
2. TECHNOLOGY STACK
Backend
| Layer | Technology | Version | Why |
|---|---|---|---|
| Framework | FastAPI | ≥0.110 | Async Python, auto-generated OpenAPI docs, dependency injection built-in |
| Server | Uvicorn | ≥0.27 | ASGI server, handles async + WebSocket |
| ORM | SQLAlchemy 2.0 | ≥2.0.25 | Database-agnostic — same code runs on SQLite (dev) and PostgreSQL (prod) |
| Validation | Pydantic 2 | ≥2.6 | Type-safe request/response validation, automatic JSON serialization |
| Auth | python-jose + passlib | — | JWT tokens (HS256), bcrypt password hashing, 12h access / 7d refresh |
| Database (dev) | SQLite | — | Zero-config, file-based, perfect for single-server deployment |
| Database (prod) | PostgreSQL 15 | 15.x | Connection pooling (20 base + 40 overflow), production-grade |
| QR Codes | qrcode + Pillow | ≥7.4 | Material tracking — every basket/reel gets a printed QR label |
| Printing | reportlab + socket | — | Direct Epson thermal printer communication over factory network |
Frontend
| Layer | Technology | Version | Why |
|---|---|---|---|
| Framework | React | 19.1 | Component-based UI, massive ecosystem |
| Language | TypeScript | 5.x | Type safety across the entire frontend |
| UI Library | Ant Design Pro 6 | 6.0 | Enterprise-grade components — tables, forms, layouts, charts out of the box |
| Build | UmiJS Max | 4.0.7 | Ant Design Pro’s build system — routing, state management, request handling |
| Charts | ECharts | 6.0 | Production dashboards, stock projections, machine utilization |
| Animation | Framer Motion | 12.x | UI transitions and micro-interactions |
| Drag & Drop | react-dnd | 16.x | Cable design playground, palette arrangement |
Why Ant Design Pro? The single most important framework choice. A cable factory ERP is 80% tables, forms, and CRUD screens. Ant Design Pro provides production-ready versions of all of these with built-in Turkish localization, responsive layouts, role-based menus, and a dark mode. Writing these from scratch would have added months. The trade-off: the frontend is tied to the Ant Design ecosystem.
Infrastructure
| Component | Technology | Notes |
|---|---|---|
| Dev Server | Mac Mini | Factory floor, local network only |
| Deployment | Direct | No containers — Python + Node running natively on the server |
| HTTPS | Self-signed / Let’s Encrypt | Factory network HTTPS for mobile devices |
| AI Service | Separate Python service | Multi-provider (Gemini, OpenAI, Claude), read-only access, audit-logged |
3. DESIGN RULES
These rules were not written on day one — they crystallized over months of building, breaking, and rebuilding.
Rule 1: Turkish-First Interface
Every UI label, every error message, every notification is in Turkish. Factory operators do not speak English. The Ant Design locale is locked to Turkish. AI prompts are Turkish. The only English in the system is in code comments, variable names, and this documentation.
Rule 2: QR Code Everything
Every physical object that moves through the factory gets a QR code printed on entry. Raw copper baskets, tinned copper baskets, reels, finished cables — all scannable. The QR code links to the full history of that object in the system. This enables traceability from raw material to customer shipment.
Rule 3: Three User Roles, No More
| Role | Access | Interface | Device |
|---|---|---|---|
| Super Admin | Everything — including raw database queries, force delete, AI settings | Desktop | PC |
| Lab User | Test standards, test results, material inspection, quality control | Desktop | PC |
| Operator | Production sessions, material scanning, machine-specific views | Mobile-first | Phone/Tablet |
Rule 4: Design Drives Production
The cable design is the single source of truth. When a design is created in the Cable Playground, it encodes the complete production flow — every machine step, every material requirement, every test. Orders reference designs. Work cards are generated from designs. Material calculations walk through the design’s production flow. Nothing is manually configured per-order.
Rule 5: No Premature Optimization
SQLite in development. PostgreSQL when needed. No Redis cache until there’s a measured performance problem. No message queue until synchronous processing is proven insufficient. Build the simplest thing that works, measure, then optimize.
4. API DESIGN
The backend exposes 25+ router groups, all mounted on a single FastAPI application running on port 8000. Every endpoint follows the same pattern:
@router.get("/endpoint") async def get_something( db: Session = Depends(get_database), # DB session injected current_user: User = Depends(get_current_user) # Auth injected ): # Business logic here return result
Key Patterns
- Dependency injection for everything. Database sessions via
Depends(get_database), authentication viaDepends(get_current_user), role checks viaDepends(require_super_admin). No global state. - Pydantic schemas for validation. Every request body and response has a typed schema. Create, Update, and Response variants per model. FastAPI auto-generates OpenAPI documentation from these.
- Service layer for business logic. Complex calculations (material requirements, work card generation, stock projections) live in
services/, not in route handlers. Routes handle HTTP; services handle domain logic. - Factory network CORS. The CORS configuration generates 1000+ origin combinations to support HTTP and HTTPS access from any device on the 192.168.x.x and 10.0.0.x factory networks.
Router Organization
| Prefix | Module | Routes |
|---|---|---|
| /api/auth | Authentication | Login, register, refresh, user management |
| /api/materials | Raw Materials | CRUD, QR generation, photo upload, weight tracking |
| /api/suppliers | Suppliers | CRUD, material catalog, quality rating |
| /api/siparis | Orders | Order CRUD, cable items, delivery planning, payments |
| /api/production | Production | Sessions, work cards, operator assignments |
| /api/teknik | Technical | Machine config, cable design, standards, markings |
| /api/stock | Stock | Half-product stock, projections, product codes |
| /api/ai | AI Assistant | Chat, search (read-only, audit-logged) |
5. DATABASE STRATEGY
The database was not designed upfront. It grew organically as each module was built. This is intentional — the domain was too complex to design a schema before understanding it.
SQLite → PostgreSQL Migration Path
Development
Production
If needed
SQLAlchemy ORM ensures the same Python code runs against both databases. The switch requires only changing the DATABASE_URL environment variable. Connection pooling is configured for production: 20 base connections, 40 overflow, hourly recycling, pre-ping validation.
Migration Strategy
Migrations use direct SQL scripts (not Alembic) — CREATE TABLE IF NOT EXISTS and ALTER TABLE with transaction rollback on failure. 19 migration files covering orders, users, machines, materials, stock, printing, and AI audit tables. Each migration is idempotent and can be re-run safely.
Why not Alembic? For a single-developer system where the schema evolves rapidly, hand-written migration scripts with IF NOT EXISTS guards are simpler and more predictable than auto-generated migrations. The trade-off: no automatic schema diff detection. This is acceptable when the developer writes both the model and the migration.
6. FRONTEND ARCHITECTURE
The frontend is a single React application built with Ant Design Pro, serving all modules through role-based menus.
Page Organization
src/pages/ ├── Dashboard/ # Ana Panel — overview + AI chat ├── Lab/ # Laboratuvar — tests, material inspection ├── Teknik/ # Teknik — cable design, machines, standards │ ├── CablePlayground/ # Interactive cable design tool │ ├── Machines/ # Machine configuration │ └── Standards/ # Test standard management ├── Siparis/ # Sipariş — orders, customers ├── Production/ # Üretim — planning, machine interfaces │ └── Machines/ # Per-machine production UIs │ ├── KabatelCekme/ │ ├── Kalaylama/ │ ├── IncetelCekme/ │ ├── Buncher/ │ ├── Extruder/ │ └── ElectronBeam/ ├── Stok/ # Stok — projections, product stock ├── Hammadde/ # Hammadde — material entry, orders └── Admin/ # Admin — printers, users, AI settings
Key Frontend Patterns
- ProTable everywhere. Ant Design Pro’s
ProTablecomponent handles server-side pagination, column filtering, expandable rows, and search. 90% of the list/detail views use this single component. - Route-based access control. Each route has an
accessflag (canDashboard,canLab,canProduction, etc.). Menus auto-hide based on user role. - Dynamic API URL. The frontend detects whether it’s running over HTTPS (proxy mode) or HTTP (direct mode) and adjusts the API base URL. On the factory network, it discovers the backend IP automatically.
- Dark mode. Full dark theme support via Ant Design CSS variables, persisted in localStorage.
7. AI INTEGRATION
A separate Python service provides AI-powered search and chat, accessible from the dashboard. The AI has read-only access to the entire database through 92 API endpoint tools.
Multi-Provider
Dozens of API providers supported. Provider-agnostic architecture with a base class and model registry system.
Tool-Based Access
AI doesn’t query the database directly. It calls the same REST API endpoints that the frontend uses, but with a dedicated read-only service account.
Audit Logged
Every AI query, every tool call, every response is logged to the ai_audit_logs table. Full traceability of what the AI accessed.
Why read-only? An AI assistant that can modify production data in a factory is a liability. One hallucinated material deletion could halt production. The AI can search, summarize, and answer questions — but cannot create, update, or delete anything, for now.
8. NETWORK & DEPLOYMENT
The system runs on a Mac Mini on the factory floor, accessible to all devices on the local network.
Operators use mobile Chrome on phones/tablets to scan QR codes and log production. Managers use desktop browsers for order management, design, and reporting. Both access the same backend on the same network. HTTPS is enabled for mobile device security (camera access for QR scanning requires secure context).
9. THE EVOLUTION PATH
The architecture has a planned evolution path if scale ever demands it:
MODULAR
MONOLITH
Current
DATABASE
SEPARATION
Per-module DBs
SERVICE
EXTRACTION
Critical modules
TRUE
MICROSERVICES
Only if needed
Each stage is a fallback, not a goal. The modular monolith should remain the architecture unless specific, measured problems demand the next stage. Don’t fix what isn’t broken.