Architecture Overview
Clean Architecture layers, backend pipeline, frontend SOLID flow, and module boundary rules.
SCRIPE follows a strict Clean Architecture with four layers: Presentation, Application, Domain, and Infrastructure. The dependency rule ensures inner layers never depend on outer layers. This architecture is applied consistently across both backend (.NET) and frontend (Next.js).
Clean Architecture Layers
| Layer | Responsibility | Key Technologies | Dependency Rule |
|---|---|---|---|
| Presentation | UI rendering, user interaction, routing | Next.js 16, React, Shadcn/ui | → Application only |
| Application | Use cases, orchestration, validation | AstraFlow mediator, FluentValidation, explicit DTO mapping | → Domain only |
| Domain | Business rules, entities, interfaces | Pure C# / TypeScript, no dependencies | → Nothing (innermost) |
| Infrastructure | Data access, external APIs, caching | EF Core, Redis, SignalR, Hangfire | → Domain (implements interfaces) |
Backend Architecture
The backend follows a request pipeline architecture where every HTTP request flows through middleware, controllers, SCRIPE mediator behaviors, and finally the CQRS handler. This ensures consistent validation, auditing, and error handling.
Frontend Architecture
The frontend uses a SOLID View/ViewModel pattern where Views are pure UI (no state, no logic) and ViewModels contain all business logic. The connector pattern separates Next.js routing (Server Components) from application logic (Client Components).
Module Boundaries
Modules have strict code boundaries. This supports future extraction, but extraction is not the same as proven production microservices readiness.
With Module Boundaries
- Clear dependency graph
- Isolated failures — one module crash doesn't affect others
- Easy extraction to separate repository or microservice
- Team autonomy — parallel development
- Incremental builds and tests
Without Module Boundaries
- Spaghetti imports across modules
- Breaking one module breaks all
- Cannot extract to separate repo
- Merge conflicts everywhere
- Full rebuild on any change
Cross-Module Communication
| Pattern | When to Use | Example |
|---|---|---|
| URL Navigation | Module A links to Module B's page | Link href={`/vendor/${vendorId}`} |
| Shared IDs Only | Store reference ID without embedding entity | assignedVendorId: z.string().uuid() |
| Core Event Bus | React to events across modules (future) | eventBus.emit('employee:created', data) |
| Core Shared Kernel | Reusable utilities needed by many modules | Move to @core/ namespace |