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