Application Architecture
@azure-net/kit implements a hybrid architecture that combines principles of Domain Driven Design (DDD) and Clean Architecture. This allows for creating loosely coupled, testable, and easily extensible systems.
General Architecture Principles
1. Layer Separation
The architecture is built on four main layers:
- Domain - business logic and domain models
- Application - application services and orchestration
- Infrastructure - external dependencies and adapters
- Delivery - providing data to UI elements
2. Dependency Inversion
Inner layers do not depend on outer layers. Dependencies are directed inward toward the domain.
3. Context Separation
The system is divided into Bounded Contexts, each with its own domain area.
Example Context Division
src/
├── app/
│ ├── contexts/ # Bounded Contexts
│ │ ├── admin/ # Administrative context
│ │ └── account/ # Client context
│ ├── core/ # Base components for package setup and operation (used in contexts as core)
│ └── shared/ # shared layer - common and reusable elements that don't belong to a specific module or contextDetailed Context Structure
Each context has the following structure:
Domain Layer (contexts/{context}/domain/)
Purpose: Contains business logic and domain models.
domain/
├── {aggregate}/ # Domain aggregate
│ ├── model/ # Data models
│ │ └── index.ts # Entity interfaces
│ ├── enums/ # Enumerations
│ ├── constants/ # Constants related to business entity
│ ├── ports/ # Ports (interfaces)
│ │ └── index.ts # Contracts for external dependencies
│ └── index.ts # Aggregate exportWhat's contained in Domain:
Model (Data models):
- Domain entity interfaces
- Example:
IUser,IProduct
Enums (Enumerations):
- Enums related to the entity
- Example: user statuses, user roles
Ports (Ports):
- Interfaces for external dependencies
- Define contracts for repositories and services
- Include data structures for input/output
- Serve as a unified description of what exactly the domain expects from the external world and what it provides to the outside, while maintaining domain logic isolation.
- Example:
IUserRepository,IUserCreateRequest,IUserCollectionResponse,IUserCollectionQuery
Infrastructure Layer (contexts/{context}/infrastructure/)
Purpose: Implements technical details and external integrations.
infrastructure/
├── http/
│ └── repositories/ # Repository implementations
│ ├── UserRepository.ts
│ ├── AuthRepository.ts
│ └── index.ts
├── providers/ # Infrastructure providers
│ ├── InfrastructureProvider.ts
│ └── index.ts
└── index.tsWhat's contained in Infrastructure:
Repositories (Repositories):
- Implement ports from Domain layer
- Encapsulate data access logic
- Transform external data into domain models
- Work with HTTP API
Providers (Providers):
- Provide access to infrastructure layer components for other layers
- Create and configure repositories and other components
- Dependency injection (DataSources, HTTP clients)
Application Layer (contexts/{context}/application/)
Purpose: Working with scenarios that implement business logic at the application level.
application/
├── services/ # Application services
│ ├── JobRequestService.ts
│ ├── AuthService.ts
│ └── index.ts
├── providers/ # Application providers
│ ├── ApplicationProvider.ts
│ └── index.ts
└── index.tsWhat's contained in Application:
Services (Services):
- Implement repository methods
- Create use cases and domain business logic implementation
- Orchestrate calls to domain and infrastructure
- Use ports from Domain layer
- Example: user creation scenario, user update scenario, etc.
Providers (Providers):
- Provide access to application layer components for other layers
- Create and configure services
- Dependency injection from infrastructure layer
Delivery Layer (contexts/{context}/delivery/)
Purpose: Handles user actions and provides data from the application layer.
delivery/
├── {feature}/ # Functional area
│ ├── {Feature}Presenter.ts # Presenter
│ ├── schema/ # Validation schemas
│ │ ├── CreateSchema.ts
│ │ ├── UpdateSchema.ts
│ │ └── index.ts
│ └── consts/ # Constants
└── auth/ # AuthorizationWhat's contained in Delivery:
Presenters (Presenters):
- Connect UI with Application layer
- Handle user actions
- Manage UI state
Schema (UI validation schemas):
- Validate incoming data
- Transform data for API
- Ensure type safety
Everything required for UI (ui-constants, stores for states, etc.)
Core Components (app/core/)
Purpose: Base components for package setup and operation (used in contexts as core).
core/
├── datasources/ # Base data sources
├── providers/ # Base providers
├── presenters/ # Base presenters
├── middleware/ # Components for setup and connection of universal middlewares to the project
├── responses/ # Base response handlers
└── schemas/ # Base UI validation schema settingsWhat's contained in Core:
DataSources:
- HTTP clients for API interaction
- Connection configuration
- Error handling and retry logic
Providers:
- Global dependency providers
- DataSource creation and configuration
Presenters:
- Base classes for presenters and their injection
etc.
Shared Components (app/shared/)
Purpose: common and reusable elements that don't belong to a specific module or context
shared/
├── helpers/ # Global helper functions
├── stores/ # Global state stores
├── types/ # Common types
└── etc.../ # Other folders required in shared for your projectArchitecture Working Principles
1. Dependency Injection through Providers
The system uses the Provider Pattern for dependency management:
// Creating a provider with dependencies
export const ApplicationProvider = createBoundaryProvider('{ContextName}ApplicationProvider', {
dependsOn: { InfrastructureProvider },
register: ({ InfrastructureProvider }) => ({
// Service registration
UserService: () => new UserService(InfrastructureProvider.UserRepository),
SystemHealthService: () => new SystemHealthService()
}),
boot: ({SystemHealthService}) => {
// Allows access to already registered services and performs initialization, starts background processes or sets up observers.
SystemHealthService.startMonitoring();
}
});2. Ports as Contracts
Ports define interfaces that must be implemented in external layers:
// Port in Domain layer
export interface IUserCreateRequest {
name: string;
}
export interface IUserCreateResponse {
token: string;
}
export interface IUserRepository {
create(request: IUserCreateRequest): Promise<IUserCreateResponse>;
}
// Implementation in Infrastructure layer
export class UserRepository implements IUserRepository {
create(request: IUserCreateRequest): Promise<IUserCreateResponse> {
// Concrete implementation
}
}3. Presenters as UI Coordinators
Presenters connect the user interface with business logic:
export const UserPresenter = AppPresenter('{ContextName}UserPresenter', ({ createAsyncAction }) => {
const { UserService } = ApplicationProvider();
const create = async (data: IUserCreateRequest) => {
return await createAsyncAction(() => UserService.create(data));
};
return { create };
});Development Recommendations
When adding a new feature:
- Define a model in the Domain layer
- Create a port for external dependencies
- Create a repository in the Infrastructure layer
- Implement a service in the Application layer
- Update providers to bind dependencies
- Configure a presenter in the Delivery layer
Advantages of This Architecture
- Testability - each layer can be tested independently
- Extensibility - easy to add new features without changing existing code
- Clarity - clear separation of responsibilities
- Scalability - support for multiple contexts
This architecture ensures clear separation of responsibilities and allows creating reliable, testable, and easily maintainable applications.