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

Clean Architecture Layers
Presentation Layer — Next.js Views + ViewModels
Infrastructure Layer — EF Core + Repos + External Services
Application Layer — CQRS Commands/Queries + Behaviors
Depends on
Domain Layer — Entities + Interfaces + Specifications
LayerResponsibilityKey TechnologiesDependency Rule
PresentationUI rendering, user interaction, routingNext.js 16, React, Shadcn/ui→ Application only
ApplicationUse cases, orchestration, validationAstraFlow mediator, FluentValidation, explicit DTO mapping→ Domain only
DomainBusiness rules, entities, interfacesPure C# / TypeScript, no dependencies→ Nothing (innermost)
InfrastructureData access, external APIs, cachingEF 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.

Backend Request Pipeline
HTTP Request
Middleware Stack
Controller
AstraFlow mediator Send
Validation
Audit Behavior
CQRS Handler
Result<T>

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).

Frontend SOLID Architecture
page.tsx (Connector)
renders
View (Pure UI)
uses hook
ViewModel (Logic)
calls
Repository
fetches
API Service

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

PatternWhen to UseExample
URL NavigationModule A links to Module B's pageLink href={`/vendor/${vendorId}`}
Shared IDs OnlyStore reference ID without embedding entityassignedVendorId: z.string().uuid()
Core Event BusReact to events across modules (future)eventBus.emit('employee:created', data)
Core Shared KernelReusable utilities needed by many modulesMove to @core/ namespace