Architecture: Ports & Adapters
Architecture: Ports & Adapters
Relevant source files
The following files were used as context for generating this wiki page:
The @primebrick/sdk is built on the Hexagonal Architecture (also known as Ports & Adapters) pattern. The primary goal of this design is to make the SDK entirely database-agnostic and decoupled from specific Data Access Layer (DAL) implementations README.md:3-7.
By defining abstract "Ports" (interfaces), the SDK core logic remains pure and testable, while the consuming microservice is responsible for providing "Adapters" (concrete implementations) that bridge the SDK to specific infrastructure like PostgreSQL, MSSQL, or MariaDB src/ports/database-port.ts:4-6.
Dependency Inversion Principle
The SDK applies the Dependency Inversion Principle (DIP) to ensure that high-level modules (like the Migration Runner or Config Loader) do not depend on low-level modules (like a specific database driver). Instead, both depend on abstractions src/ports/config-repository-port.ts:4-7.
Data Flow: SDK to Infrastructure
The following diagram illustrates how the SDK interacts with external systems through ports.
Entity Mapping: SDK Ports to Infrastructure
Code
Sources: src/ports/config-repository-port.ts:8-14, src/ports/database-port.ts:11-17, src/ports/health-check-port.ts:8-11, src/ports/service-registry-port.ts:10-19
The Four Port Interfaces
The SDK defines four primary ports that must be implemented by the consuming application to enable full functionality.
1. ConfigRepositoryPort
Used by the ConfigLoader to fetch application settings from a persistent store src/ports/config-repository-port.ts:4-7.
| Method | Description |
|---|---|
findAll() | Returns all configuration rows as an array of { key, value } pairs. Value is null if the key exists without a value src/ports/config-repository-port.ts:13-13. |
Sources: src/ports/config-repository-port.ts:8-14
2. DatabasePort
A low-level interface for executing parameterized SQL. This is the backbone of the SDK's migration runner (applyPatches) src/ports/database-port.ts:4-6.
| Method | Description |
|---|---|
query<T>(text, params?) | Executes a SQL statement. Returns an object containing a rows array. Used for transaction control (BEGIN, COMMIT) and patch execution src/ports/database-port.ts:13-16. |
Sources: src/ports/database-port.ts:11-17
3. ServiceRegistryPort
Used by the ServiceRegistrar to manage the lifecycle and heartbeat of the microservice within a centralized registry table src/ports/service-registry-port.ts:4-9.
| Method | Description |
|---|---|
findByCode(code) | Retrieves a service record by its unique code src/ports/service-registry-port.ts:12-12. |
insert(row) | Adds a new service entry to the registry src/ports/service-registry-port.ts:15-15. |
updateByCode(code, row) | Updates an existing service record (e.g., updating the heartbeat timestamp) src/ports/service-registry-port.ts:18-18. |
Sources: src/ports/service-registry-port.ts:10-19
4. HealthCheckPort
A specialized port for connectivity pings. It allows the SDK's HealthCheck utility to verify database availability without knowing the specific SQL dialect src/ports/health-check-port.ts:4-7.
| Method | Description |
|---|---|
ping() | Returns a boolean indicating if the database is reachable (e.g., by running SELECT 1) src/ports/health-check-port.ts:9-10. |
Sources: src/ports/health-check-port.ts:8-11
Implementing Adapters
Consumers write adapter implementations by wrapping their chosen database driver or Data Access Layer.
Implementation Pattern
The consumer provides these implementations at startup, injecting them into the SDK's functional modules.
Code Entity Relationship: Adapter Implementation
Code
Sources: src/ports/database-port.ts:1-17, README.md:7-7
Key Requirements for Adapters:
- Error Handling: Adapters should catch driver-specific errors and allow them to bubble up or wrap them in a way the SDK can log.
- Type Safety: The
DatabasePortuses generics (<T = unknown>) to allow consumers to maintain type safety when fetching rows src/ports/database-port.ts:16-16. - Statelessness: Ports are generally designed to be stateless, acting as a pass-through to the underlying connection pool or client src/ports/database-port.ts:8-10.
Sources: src/ports/database-port.ts:1-17, src/ports/config-repository-port.ts:1-14, src/ports/service-registry-port.ts:1-19, src/ports/health-check-port.ts:1-11