-
Notifications
You must be signed in to change notification settings - Fork 57
Expand file tree
/
Copy pathContainer.ts
More file actions
183 lines (158 loc) · 6.66 KB
/
Copy pathContainer.ts
File metadata and controls
183 lines (158 loc) · 6.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/**
* Composition root for the backend.
*
* `Container` is a lazy singleton that wires up the dependency graph:
* - selects a {@link CreditLineRepository} implementation based on
* `DATABASE_URL` and `NODE_ENV` (Postgres in production, in-memory
* everywhere else),
* - constructs the service layer that route handlers depend on,
* - instantiates the Soroban client and the reconciliation pipeline,
* - exposes a graceful {@link Container.shutdown} method that the boot
* harness invokes on `SIGTERM` / `SIGINT`.
*
* Tests bypass env-var gymnastics by calling {@link Container.setRepositories}
* with stubs, or by reaching into the container via `getInstance()` after
* setting `NODE_ENV=test`.
*
* See `docs/ARCHITECTURE.md` §1 (Wiring) for the boot order.
*/
import { type CreditLineRepository } from "../repositories/interfaces/CreditLineRepository.js";
import { type RiskEvaluationRepository } from "../repositories/interfaces/RiskEvaluationRepository.js";
import { type TransactionRepository } from "../repositories/interfaces/TransactionRepository.js";
import { getConnection, type DbClient } from "../db/client.js";
import { InMemoryCreditLineRepository } from "../repositories/memory/InMemoryCreditLineRepository.js";
import { InMemoryRiskEvaluationRepository } from "../repositories/memory/InMemoryRiskEvaluationRepository.js";
import { InMemoryTransactionRepository } from "../repositories/memory/InMemoryTransactionRepository.js";
import { PostgresCreditLineRepository } from "../repositories/postgres/PostgresCreditLineRepository.js";
import { CreditLineService } from "../services/CreditLineService.js";
import { RiskEvaluationService } from "../services/RiskEvaluationService.js";
import { createRiskProvider } from "../services/providers/providerFactory.js";
import { ReconciliationService } from "../services/reconciliationService.js";
import { ReconciliationWorker } from "../services/reconciliationWorker.js";
import { MockSorobanClient, resolveSorobanConfig } from "../services/sorobanClient.js";
import { defaultJobQueue } from "../services/jobQueue.js";
export class Container {
private static instance: Container;
// Database client
private _dbClient?: DbClient;
// Repositories
private _creditLineRepository!: CreditLineRepository;
private _riskEvaluationRepository!: RiskEvaluationRepository;
private _transactionRepository!: TransactionRepository;
// Services
private _creditLineService: CreditLineService;
private _riskEvaluationService: RiskEvaluationService;
private _reconciliationService: ReconciliationService;
private _reconciliationWorker: ReconciliationWorker;
private constructor() {
// Initialize repositories based on environment
this.initializeRepositories();
// Initialize services
this._creditLineService = new CreditLineService(this._creditLineRepository);
this._riskEvaluationService = new RiskEvaluationService(
this._riskEvaluationRepository,
createRiskProvider(),
);
// Initialize Soroban client and reconciliation services
const sorobanConfig = resolveSorobanConfig();
const sorobanClient = new MockSorobanClient(sorobanConfig);
this._reconciliationService = new ReconciliationService(
this._creditLineRepository,
sorobanClient,
defaultJobQueue,
);
this._reconciliationWorker = new ReconciliationWorker(
this._reconciliationService,
defaultJobQueue,
);
}
private initializeRepositories(): void {
const useDatabase = process.env.DATABASE_URL && process.env.NODE_ENV !== 'test';
if (useDatabase) {
// Use PostgreSQL repositories
this._dbClient = getConnection();
this._creditLineRepository = new PostgresCreditLineRepository(this._dbClient);
// TODO: Implement PostgreSQL versions of other repositories
this._riskEvaluationRepository = new InMemoryRiskEvaluationRepository();
this._transactionRepository = new InMemoryTransactionRepository();
} else {
// Use in-memory repositories (for development/testing)
this._creditLineRepository = new InMemoryCreditLineRepository();
this._riskEvaluationRepository = new InMemoryRiskEvaluationRepository();
this._transactionRepository = new InMemoryTransactionRepository();
}
}
public static getInstance(): Container {
if (!Container.instance) {
Container.instance = new Container();
}
return Container.instance;
}
// Repository getters
get creditLineRepository(): CreditLineRepository {
return this._creditLineRepository;
}
get riskEvaluationRepository(): RiskEvaluationRepository {
return this._riskEvaluationRepository;
}
get transactionRepository(): TransactionRepository {
return this._transactionRepository;
}
// Service getters
get creditLineService(): CreditLineService {
return this._creditLineService;
}
get riskEvaluationService(): RiskEvaluationService {
return this._riskEvaluationService;
}
get reconciliationService(): ReconciliationService {
return this._reconciliationService;
}
get reconciliationWorker(): ReconciliationWorker {
return this._reconciliationWorker;
}
// Method to replace repositories (useful for testing or switching to DB implementations)
public setRepositories(repositories: {
creditLineRepository?: CreditLineRepository;
riskEvaluationRepository?: RiskEvaluationRepository;
transactionRepository?: TransactionRepository;
}): void {
if (repositories.creditLineRepository) {
this._creditLineRepository = repositories.creditLineRepository;
this._creditLineService = new CreditLineService(
this._creditLineRepository,
);
}
if (repositories.riskEvaluationRepository) {
this._riskEvaluationRepository = repositories.riskEvaluationRepository;
this._riskEvaluationService = new RiskEvaluationService(
this._riskEvaluationRepository,
createRiskProvider(),
);
}
if (repositories.transactionRepository) {
this._transactionRepository = repositories.transactionRepository;
}
}
/**
* Shutdown internal services and close database connections.
*/
public async shutdown(): Promise<void> {
console.log("[Container] Shutting down internal services...");
// Stop reconciliation worker
if (this._reconciliationWorker.isRunning()) {
this._reconciliationWorker.stop();
}
// Stop job queue
defaultJobQueue.stop();
if (this._dbClient) {
try {
await this._dbClient.end();
console.log("[Container] Database connection closed.");
} catch (error) {
console.error("[Container] Error closing database connection:", error);
}
}
console.log("[Container] All services shut down.");
}
}