Skip to main content

Case Clinical Architecture Guide

Overview

This guide provides a comprehensive walkthrough of the Case Clinical application's multi-layered architecture, detailing how data flows from the database through various layers to the frontend.

Architecture Layers

1. Database Layer (Prisma)

Location: libs/api/core/data-access/src/prisma/

The database layer uses Prisma ORM with SQL Server as the primary database.

Key Components:

  • Schema Definition: schema.prisma - Defines all database models and relationships
  • Database Provider: SQL Server with shadow database for migrations
  • Generated Client: Prisma Client with binary targets for multiple environments

Example Model:

model FormLayout {
id String @id @default(cuid())
createdAt DateTime? @default(now())
updatedAt DateTime? @default(now())
name String?
config String? @db.Text
type Int @default(0)
// ... additional fields
}

Key Features:

  • Automatic CUID generation for IDs
  • Timestamp tracking (createdAt, updatedAt)
  • Relationship mapping between entities
  • Support for complex data types (Text, JSON)

2. NestJS Backend Layer

2.1 Core Data Access Services

Location: libs/api/core/data-access/src/lib/

ApiCoreDataAccessService
  • Purpose: Primary Prisma client wrapper
  • Extends: PrismaClient
  • Features:
    • Module lifecycle management (OnModuleInit, OnModuleDestroy)
    • Pusher integration for real-time events
    • Custom database procedures (e.g., getPatientMRN())
    • Transaction support with timeout configuration
@Injectable()
export class ApiCoreDataAccessService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
constructor() {
super({
transactionOptions: {
maxWait: 15000,
timeout: 300000,
},
})
}
}
ApiCoreSharedService
  • Purpose: High-level business operations wrapper
  • Features:
    • Azure Blob Storage integration
    • Document management
    • File processing capabilities
    • Batch operations support

2.2 Feature-Specific Data Access Pattern

Location: libs/api/{entity}/data-access/

Each domain entity follows a consistent pattern:

Service Structure:
@Injectable()
export class Api{Entity}DataAccessUserService {
constructor(private readonly data: ApiCoreSharedService) {}

async user{Entities}(userId: string, input?: UserList{Entity}Input) {
return this.data.{entity}.findMany({
where: {
AND: [{
name: { contains: input?.name },
}]
},
take: input?.limit,
skip: input?.skip
})
}
}

2.3 GraphQL Resolver Layer

Location: libs/api/{entity}/feature/

Resolver Pattern:
@Resolver()
@UseGuards(GqlAuthGuard)
export class Api{Entity}FeatureUserResolver {
constructor(private readonly service: Api{Entity}DataAccessUserService) {}

@Query(() => [{Entity}], { nullable: true })
user{Entities}(
@CtxUser() user: User,
@Args({ name: 'input', type: () => UserList{Entity}Input, nullable: true }) input?: UserList{Entity}Input,
) {
return this.service.user{Entities}(user.id, input)
}
}
Key Features:
  • JWT-based authentication with GqlAuthGuard
  • User context injection via @CtxUser()
  • Type-safe GraphQL schema generation
  • Input validation through DTOs

3. Business Logic Layer

3.1 Core Business Logic Libraries

Location: libs/business-logic/

The business logic layer is organized into specialized modules:

Available Modules:
  • actions/: Business action implementations
  • common/: Shared utilities and helpers
  • configuration/: Application configuration management
  • core/: Core business logic services
  • error-handling/: Centralized error management
  • foundation/: Base classes and interfaces
  • http-service/: HTTP communication utilities
  • logging/: Structured logging services
  • notifications/: Notification management
  • rules-engine/: Business rules validation
  • validation/: Input validation logic

3.2 Frontend Business Logic Pattern

Location: libs/web/{entity}/shared/

Business Provider Service:
@Injectable({providedIn: 'root'})
export class {Entity}BusinessProviderService extends ServiceBase {
constructor(
logger: LoggingService,
public data: WebCoreDataAccessService,
serviceContext: ServiceContext
) {
super('NotificationService.{Entity}BusinessProviderService', logger, serviceContext)
}

create{Entity}(input: UserCreate{Entity}Input): Observable<{Entity}> {
const action = new Create{Entity}Action(input);
action.Do(this);
return action.response;
}
}
Business Action Pattern:
export class Create{Entity}Action extends {Entity}BusinessActionBase<{Entity}> {
constructor(private input: UserCreate{Entity}Input) {
super('Create{Entity}Action')
}

preValidateAction() {
this.validationContext.addRule(
new Create{Entity}InputIsValidRule(
'InputIsNotNull',
'The input information is not valid.',
this.input,
this.showRuleMessages
)
)
}

performAction() {
this.response = this.businessProvider.data.userCreate{Entity}({ input: this.input }).pipe(
switchMap((result) => of(result.data.created))
)
}
}
Key Features:
  • Action-Based Architecture: Each business operation is encapsulated in an action class
  • Validation Pipeline: Pre-validation rules before execution
  • Service Context: Shared logging and service context
  • Observable Responses: RxJS-based reactive programming

4. Frontend State Management Layer (Redux/NgRx)

4.1 Component Store Pattern

Location: libs/web/{entity}/shared/{entity}.store.ts

Store Structure:
export interface {Entity}FeatureState {
errors?: any
loading?: boolean
item?: {Entity}
done: boolean
formName?: string
{entities}: {Entity}[]
searchQuery?: string
paging?: CorePaging
}

@Injectable()
export class Web{Entity}FeatureStore extends ComponentStore<{Entity}FeatureState> {
constructor(
private readonly data: WebCoreDataAccessService,
private readonly router: Router,
private readonly route: ActivatedRoute,
private readonly toast: WebUiToastService,
private readonly formService: FormService,
private readonly {entity}Service: {Entity}Service
) {
super({
loading: false,
{entities}: [],
done: false,
searchQuery: '',
formName: undefined,
paging: {
limit: 10000,
skip: 0,
},
})
}
}

4.2 Data Access Integration

Location: libs/web/auth/data-access/

Web Data Access Service:
@Injectable()
export class WebAuthDataAccessService {
constructor(public readonly data: WebCoreDataAccessService) {}

me() {
return this.data.me()
}

login(input: LoginInput) {
return this.data.login({ input })
}
}
Key Features:
  • Component Store: NgRx Component Store for local state management
  • Reactive State: RxJS observables for state changes
  • Form Integration: Integration with reactive forms
  • Navigation: Router integration for state-based navigation

Complete Data Flow

1. Database → NestJS API

Prisma Schema → Prisma Client → ApiCoreDataAccessService → ApiCoreSharedService

2. NestJS API → GraphQL

Data Access Service → Feature Resolver → GraphQL Schema → API Endpoint

3. Business Logic Integration

Frontend Action → Business Provider Service → Validation Rules → Data Access Service

4. Frontend State Management

GraphQL Response → Component Store → Reactive State → UI Components

Key Architectural Patterns

1. Layered Architecture

  • Clear separation of concerns across layers
  • Dependency injection throughout the stack
  • Consistent patterns across all entities

2. Action-Command Pattern

  • Business operations encapsulated as actions
  • Validation pipeline before execution
  • Centralized error handling and logging

3. Repository Pattern

  • Data access abstraction through services
  • Consistent CRUD operations across entities
  • Type-safe database operations

4. Observer Pattern

  • RxJS observables for reactive programming
  • Event-driven state management
  • Real-time updates through Pusher integration

5. Factory Pattern

  • Consistent service and component creation
  • Dependency injection container management
  • Modular architecture with clear boundaries

Development Guidelines

1. Adding New Entities

  1. Define Prisma model in schema.prisma
  2. Create data access service in libs/api/{entity}/data-access/
  3. Implement GraphQL resolver in libs/api/{entity}/feature/
  4. Create business provider service in libs/web/{entity}/shared/
  5. Implement component store in libs/web/{entity}/shared/

2. Business Logic Implementation

  1. Extend {Entity}BusinessActionBase for new actions
  2. Implement validation rules in preValidateAction()
  3. Execute business logic in performAction()
  4. Handle errors through centralized error handling

3. State Management

  1. Define feature state interface
  2. Extend ComponentStore with typed state
  3. Implement selectors for reactive data access
  4. Create effects for async operations

Best Practices

  1. Type Safety: Maintain end-to-end TypeScript types
  2. Validation: Implement validation at multiple layers
  3. Error Handling: Use centralized error management
  4. Testing: Unit tests for business logic and integration tests for data flow
  5. Documentation: Keep architectural documentation updated
  6. Performance: Use pagination and lazy loading for large datasets
  7. Security: Implement proper authentication and authorization at all layers