@mytechtoday/augment-extensions 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +33 -1
- package/README.md +3 -3
- package/augment-extensions/domain-rules/software-architecture/README.md +143 -0
- package/augment-extensions/domain-rules/software-architecture/examples/banking-layered.md +961 -0
- package/augment-extensions/domain-rules/software-architecture/examples/ecommerce-microservices.md +990 -0
- package/augment-extensions/domain-rules/software-architecture/examples/iot-eventdriven.md +882 -0
- package/augment-extensions/domain-rules/software-architecture/examples/monolith-to-microservices-migration.md +703 -0
- package/augment-extensions/domain-rules/software-architecture/examples/serverless-imageprocessing.md +957 -0
- package/augment-extensions/domain-rules/software-architecture/examples/trading-eventdriven.md +747 -0
- package/augment-extensions/domain-rules/software-architecture/module.json +119 -0
- package/augment-extensions/domain-rules/software-architecture/rules/challenges-solutions.md +763 -0
- package/augment-extensions/domain-rules/software-architecture/rules/definitions-terminology.md +409 -0
- package/augment-extensions/domain-rules/software-architecture/rules/design-principles.md +684 -0
- package/augment-extensions/domain-rules/software-architecture/rules/evaluation-testing.md +1381 -0
- package/augment-extensions/domain-rules/software-architecture/rules/event-driven-architecture.md +616 -0
- package/augment-extensions/domain-rules/software-architecture/rules/fundamentals.md +306 -0
- package/augment-extensions/domain-rules/software-architecture/rules/industry-architectures.md +554 -0
- package/augment-extensions/domain-rules/software-architecture/rules/layered-architecture.md +776 -0
- package/augment-extensions/domain-rules/software-architecture/rules/microservices-architecture.md +503 -0
- package/augment-extensions/domain-rules/software-architecture/rules/modeling-documentation.md +1199 -0
- package/augment-extensions/domain-rules/software-architecture/rules/monolithic-architecture.md +351 -0
- package/augment-extensions/domain-rules/software-architecture/rules/principles.md +556 -0
- package/augment-extensions/domain-rules/software-architecture/rules/quality-attributes.md +797 -0
- package/augment-extensions/domain-rules/software-architecture/rules/scalability-performance.md +1345 -0
- package/augment-extensions/domain-rules/software-architecture/rules/security-architecture.md +1039 -0
- package/augment-extensions/domain-rules/software-architecture/rules/serverless-architecture.md +711 -0
- package/augment-extensions/domain-rules/software-architecture/rules/skills-development.md +568 -0
- package/augment-extensions/domain-rules/software-architecture/rules/tools-methodologies.md +961 -0
- package/augment-extensions/visual-design/CHANGELOG.md +132 -0
- package/augment-extensions/visual-design/README.md +255 -0
- package/augment-extensions/visual-design/__tests__/README.md +119 -0
- package/augment-extensions/visual-design/__tests__/style-selector.test.ts +172 -0
- package/augment-extensions/visual-design/__tests__/vendor-styles.test.ts +214 -0
- package/augment-extensions/visual-design/domains/other/ai-prompt-helper.ts +157 -0
- package/augment-extensions/visual-design/domains/other/dotnet-application.ts +156 -0
- package/augment-extensions/visual-design/domains/other/linux-platform.ts +156 -0
- package/augment-extensions/visual-design/domains/other/mobile-application.ts +157 -0
- package/augment-extensions/visual-design/domains/other/motion-picture.ts +156 -0
- package/augment-extensions/visual-design/domains/other/os-application.ts +156 -0
- package/augment-extensions/visual-design/domains/other/print-campaigns.ts +158 -0
- package/augment-extensions/visual-design/domains/other/web-app.ts +157 -0
- package/augment-extensions/visual-design/domains/other/website.ts +161 -0
- package/augment-extensions/visual-design/domains/other/windows-platform.ts +156 -0
- package/augment-extensions/visual-design/domains/web-page-styles/amazon-cloudscape.ts +506 -0
- package/augment-extensions/visual-design/domains/web-page-styles/google-modern.ts +615 -0
- package/augment-extensions/visual-design/domains/web-page-styles/microsoft-fluent.ts +531 -0
- package/augment-extensions/visual-design/examples/README.md +97 -0
- package/augment-extensions/visual-design/examples/ai-prompt-generation.md +233 -0
- package/augment-extensions/visual-design/examples/basic-usage.md +216 -0
- package/augment-extensions/visual-design/examples/domain-workflows.md +257 -0
- package/augment-extensions/visual-design/examples/vendor-comparison.md +247 -0
- package/augment-extensions/visual-design/module.json +78 -0
- package/augment-extensions/visual-design/style-selector.ts +177 -0
- package/augment-extensions/visual-design/types.ts +302 -0
- package/augment-extensions/visual-design/visual-design-core.ts +469 -0
- package/augment-extensions/workflows/adr-support/README.md +227 -0
- package/augment-extensions/workflows/adr-support/__tests__/adr-validator.test.ts +203 -0
- package/augment-extensions/workflows/adr-support/adr-validator.ts +162 -0
- package/augment-extensions/workflows/adr-support/examples/complete-lifecycle-example.md +449 -0
- package/augment-extensions/workflows/adr-support/examples/integration-example.md +580 -0
- package/augment-extensions/workflows/adr-support/examples/superseding-example.md +436 -0
- package/augment-extensions/workflows/adr-support/module.json +112 -0
- package/augment-extensions/workflows/adr-support/rules/adr-creation.md +372 -0
- package/augment-extensions/workflows/adr-support/rules/beads-integration.md +443 -0
- package/augment-extensions/workflows/adr-support/rules/conflict-detection.md +486 -0
- package/augment-extensions/workflows/adr-support/rules/decision-detection.md +362 -0
- package/augment-extensions/workflows/adr-support/rules/lifecycle-management.md +427 -0
- package/augment-extensions/workflows/adr-support/rules/openspec-integration.md +465 -0
- package/augment-extensions/workflows/adr-support/rules/template-selection.md +405 -0
- package/augment-extensions/workflows/adr-support/rules/validation-rules.md +543 -0
- package/augment-extensions/workflows/adr-support/schemas/adr-config.json +191 -0
- package/augment-extensions/workflows/adr-support/schemas/adr-metadata.json +172 -0
- package/augment-extensions/workflows/adr-support/templates/business-case.md +235 -0
- package/augment-extensions/workflows/adr-support/templates/madr-elaborate.md +197 -0
- package/augment-extensions/workflows/adr-support/templates/madr-simple.md +68 -0
- package/augment-extensions/workflows/adr-support/templates/nygard.md +84 -0
- package/augment-extensions/workflows/beads/rules/workflow.md +1 -1
- package/cli/dist/utils/__tests__/adr-validator.example.d.ts +6 -0
- package/cli/dist/utils/__tests__/adr-validator.example.d.ts.map +1 -0
- package/cli/dist/utils/__tests__/adr-validator.example.js +148 -0
- package/cli/dist/utils/__tests__/adr-validator.example.js.map +1 -0
- package/cli/dist/utils/adr-validator.d.ts +65 -0
- package/cli/dist/utils/adr-validator.d.ts.map +1 -0
- package/cli/dist/utils/adr-validator.js +203 -0
- package/cli/dist/utils/adr-validator.js.map +1 -0
- package/modules.md +40 -3
- package/package.json +1 -1
|
@@ -0,0 +1,961 @@
|
|
|
1
|
+
# Banking Application Architecture Example
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This document provides a comprehensive example of a banking application built with layered architecture, focusing on security, compliance, and data integrity.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## System Context
|
|
10
|
+
|
|
11
|
+
### Business Requirements
|
|
12
|
+
|
|
13
|
+
**Functional Requirements**
|
|
14
|
+
- Account management (checking, savings, credit)
|
|
15
|
+
- Fund transfers (internal and external)
|
|
16
|
+
- Transaction history and statements
|
|
17
|
+
- Bill payments and recurring payments
|
|
18
|
+
- Loan applications and management
|
|
19
|
+
- Customer authentication and authorization
|
|
20
|
+
- Fraud detection and alerts
|
|
21
|
+
- Regulatory reporting
|
|
22
|
+
|
|
23
|
+
**Non-Functional Requirements**
|
|
24
|
+
- **Security**: Multi-factor authentication, encryption at rest and in transit
|
|
25
|
+
- **Compliance**: SOX, PCI DSS, GDPR, Basel III
|
|
26
|
+
- **Availability**: 99.95% uptime (4.38 hours downtime/year)
|
|
27
|
+
- **Data Integrity**: ACID transactions, zero data loss
|
|
28
|
+
- **Auditability**: Complete audit trail for all transactions
|
|
29
|
+
- **Performance**: Transaction processing < 500ms
|
|
30
|
+
|
|
31
|
+
### Scale Metrics
|
|
32
|
+
- 500,000 active customers
|
|
33
|
+
- 1 million accounts
|
|
34
|
+
- 100,000 transactions per day
|
|
35
|
+
- $10 billion in assets under management
|
|
36
|
+
- 24/7 operation with global presence
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Architecture Overview
|
|
41
|
+
|
|
42
|
+
### Layered Architecture Pattern
|
|
43
|
+
|
|
44
|
+
**Four-Tier Architecture**
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
48
|
+
│ Presentation Layer │
|
|
49
|
+
│ Web UI, Mobile Apps, ATM Interface, APIs │
|
|
50
|
+
└─────────────────────────────────────────────────────────────┘
|
|
51
|
+
│
|
|
52
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
53
|
+
│ Application Layer │
|
|
54
|
+
│ Use Cases, Orchestration, Transaction Coordination │
|
|
55
|
+
│ (Account Service, Transfer Service, Loan Service) │
|
|
56
|
+
└─────────────────────────────────────────────────────────────┘
|
|
57
|
+
│
|
|
58
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
59
|
+
│ Domain Layer │
|
|
60
|
+
│ Business Logic, Domain Models, Business Rules │
|
|
61
|
+
│ (Account, Transaction, Customer, Loan) │
|
|
62
|
+
└─────────────────────────────────────────────────────────────┘
|
|
63
|
+
│
|
|
64
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
65
|
+
│ Infrastructure Layer │
|
|
66
|
+
│ Database, External Services, Messaging, Security │
|
|
67
|
+
│ (PostgreSQL, Redis, Kafka, SWIFT, ACH) │
|
|
68
|
+
└─────────────────────────────────────────────────────────────┘
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Layer Responsibilities
|
|
72
|
+
|
|
73
|
+
**1. Presentation Layer**
|
|
74
|
+
- Web application (React)
|
|
75
|
+
- Mobile apps (iOS, Android)
|
|
76
|
+
- ATM interface
|
|
77
|
+
- Admin portal
|
|
78
|
+
- RESTful APIs
|
|
79
|
+
- Input validation
|
|
80
|
+
- Session management
|
|
81
|
+
|
|
82
|
+
**2. Application Layer**
|
|
83
|
+
- Account management use cases
|
|
84
|
+
- Transfer orchestration
|
|
85
|
+
- Loan processing workflows
|
|
86
|
+
- Transaction coordination
|
|
87
|
+
- Event publishing
|
|
88
|
+
- External service integration
|
|
89
|
+
|
|
90
|
+
**3. Domain Layer**
|
|
91
|
+
- Account entity and business rules
|
|
92
|
+
- Transaction processing logic
|
|
93
|
+
- Interest calculation
|
|
94
|
+
- Overdraft protection
|
|
95
|
+
- Fraud detection rules
|
|
96
|
+
- Loan approval logic
|
|
97
|
+
|
|
98
|
+
**4. Infrastructure Layer**
|
|
99
|
+
- PostgreSQL database (ACID compliance)
|
|
100
|
+
- Redis cache (session, rate limiting)
|
|
101
|
+
- Kafka message broker (event streaming)
|
|
102
|
+
- SWIFT integration (international transfers)
|
|
103
|
+
- ACH integration (domestic transfers)
|
|
104
|
+
- HSM (Hardware Security Module) for encryption
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Technology Stack
|
|
109
|
+
|
|
110
|
+
### Presentation Layer
|
|
111
|
+
- **Web**: React, TypeScript, Material-UI
|
|
112
|
+
- **Mobile**: React Native
|
|
113
|
+
- **API**: Node.js, Express, GraphQL
|
|
114
|
+
|
|
115
|
+
### Application Layer
|
|
116
|
+
- **Language**: Java 17
|
|
117
|
+
- **Framework**: Spring Boot 3.x
|
|
118
|
+
- **Security**: Spring Security, OAuth 2.0
|
|
119
|
+
|
|
120
|
+
### Domain Layer
|
|
121
|
+
- **Language**: Java 17
|
|
122
|
+
- **Patterns**: Domain-Driven Design (DDD)
|
|
123
|
+
- **Validation**: Bean Validation (JSR 380)
|
|
124
|
+
|
|
125
|
+
### Infrastructure Layer
|
|
126
|
+
- **Database**: PostgreSQL 15 (primary), PostgreSQL replicas (read)
|
|
127
|
+
- **Cache**: Redis Cluster
|
|
128
|
+
- **Message Broker**: Apache Kafka
|
|
129
|
+
- **Search**: Elasticsearch (transaction search)
|
|
130
|
+
- **File Storage**: AWS S3 (statements, documents)
|
|
131
|
+
|
|
132
|
+
### Security & Compliance
|
|
133
|
+
- **Authentication**: OAuth 2.0, OpenID Connect
|
|
134
|
+
- **MFA**: TOTP, SMS, Biometric
|
|
135
|
+
- **Encryption**: AES-256 (at rest), TLS 1.3 (in transit)
|
|
136
|
+
- **HSM**: Thales Luna HSM
|
|
137
|
+
- **Audit**: Splunk, custom audit tables
|
|
138
|
+
|
|
139
|
+
### Infrastructure
|
|
140
|
+
- **Hosting**: AWS (multi-region)
|
|
141
|
+
- **Container**: Docker, Kubernetes (EKS)
|
|
142
|
+
- **Load Balancer**: AWS ALB
|
|
143
|
+
- **CDN**: CloudFront
|
|
144
|
+
- **Monitoring**: Datadog, CloudWatch
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Implementation Details
|
|
149
|
+
|
|
150
|
+
### 1. Domain Layer - Account Entity
|
|
151
|
+
|
|
152
|
+
**Rich Domain Model**
|
|
153
|
+
|
|
154
|
+
```java
|
|
155
|
+
// src/main/java/com/bank/domain/account/Account.java
|
|
156
|
+
package com.bank.domain.account;
|
|
157
|
+
|
|
158
|
+
import com.bank.domain.common.Money;
|
|
159
|
+
import com.bank.domain.transaction.Transaction;
|
|
160
|
+
import javax.validation.constraints.*;
|
|
161
|
+
import java.math.BigDecimal;
|
|
162
|
+
import java.time.LocalDateTime;
|
|
163
|
+
import java.util.ArrayList;
|
|
164
|
+
import java.util.List;
|
|
165
|
+
|
|
166
|
+
public class Account {
|
|
167
|
+
@NotNull
|
|
168
|
+
private final AccountId id;
|
|
169
|
+
|
|
170
|
+
@NotNull
|
|
171
|
+
private final CustomerId customerId;
|
|
172
|
+
|
|
173
|
+
@NotNull
|
|
174
|
+
private final AccountType type;
|
|
175
|
+
|
|
176
|
+
@NotNull
|
|
177
|
+
private Money balance;
|
|
178
|
+
|
|
179
|
+
@NotNull
|
|
180
|
+
private AccountStatus status;
|
|
181
|
+
|
|
182
|
+
private Money overdraftLimit;
|
|
183
|
+
|
|
184
|
+
@NotNull
|
|
185
|
+
private final LocalDateTime createdAt;
|
|
186
|
+
|
|
187
|
+
private LocalDateTime closedAt;
|
|
188
|
+
|
|
189
|
+
private final List<Transaction> transactions = new ArrayList<>();
|
|
190
|
+
|
|
191
|
+
// Business logic methods
|
|
192
|
+
public void deposit(Money amount, String description) {
|
|
193
|
+
validateAccountActive();
|
|
194
|
+
validatePositiveAmount(amount);
|
|
195
|
+
|
|
196
|
+
this.balance = this.balance.add(amount);
|
|
197
|
+
|
|
198
|
+
Transaction transaction = Transaction.createDeposit(
|
|
199
|
+
this.id,
|
|
200
|
+
amount,
|
|
201
|
+
description,
|
|
202
|
+
LocalDateTime.now()
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
this.transactions.add(transaction);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
public void withdraw(Money amount, String description) {
|
|
209
|
+
validateAccountActive();
|
|
210
|
+
validatePositiveAmount(amount);
|
|
211
|
+
validateSufficientFunds(amount);
|
|
212
|
+
|
|
213
|
+
this.balance = this.balance.subtract(amount);
|
|
214
|
+
|
|
215
|
+
Transaction transaction = Transaction.createWithdrawal(
|
|
216
|
+
this.id,
|
|
217
|
+
amount,
|
|
218
|
+
description,
|
|
219
|
+
LocalDateTime.now()
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
this.transactions.add(transaction);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
public void transfer(Account targetAccount, Money amount, String description) {
|
|
226
|
+
validateAccountActive();
|
|
227
|
+
targetAccount.validateAccountActive();
|
|
228
|
+
validatePositiveAmount(amount);
|
|
229
|
+
validateSufficientFunds(amount);
|
|
230
|
+
validateDifferentAccounts(targetAccount);
|
|
231
|
+
|
|
232
|
+
this.withdraw(amount, "Transfer to " + targetAccount.getId());
|
|
233
|
+
targetAccount.deposit(amount, "Transfer from " + this.getId());
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
public Money calculateInterest(BigDecimal annualRate, int days) {
|
|
237
|
+
if (this.type != AccountType.SAVINGS) {
|
|
238
|
+
throw new IllegalStateException("Interest only applies to savings accounts");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
BigDecimal dailyRate = annualRate.divide(BigDecimal.valueOf(365), 10, BigDecimal.ROUND_HALF_UP);
|
|
242
|
+
BigDecimal interest = this.balance.getAmount()
|
|
243
|
+
.multiply(dailyRate)
|
|
244
|
+
.multiply(BigDecimal.valueOf(days));
|
|
245
|
+
|
|
246
|
+
return new Money(interest, this.balance.getCurrency());
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Validation methods
|
|
250
|
+
private void validateAccountActive() {
|
|
251
|
+
if (this.status != AccountStatus.ACTIVE) {
|
|
252
|
+
throw new AccountNotActiveException("Account " + this.id + " is not active");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private void validatePositiveAmount(Money amount) {
|
|
257
|
+
if (amount.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
|
258
|
+
throw new IllegalArgumentException("Amount must be positive");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private void validateSufficientFunds(Money amount) {
|
|
263
|
+
Money availableBalance = this.balance.add(this.overdraftLimit != null ? this.overdraftLimit : Money.ZERO);
|
|
264
|
+
|
|
265
|
+
if (availableBalance.compareTo(amount) < 0) {
|
|
266
|
+
throw new InsufficientFundsException(
|
|
267
|
+
"Insufficient funds. Available: " + availableBalance + ", Required: " + amount
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private void validateDifferentAccounts(Account other) {
|
|
273
|
+
if (this.id.equals(other.id)) {
|
|
274
|
+
throw new IllegalArgumentException("Cannot transfer to the same account");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Getters
|
|
279
|
+
public AccountId getId() { return id; }
|
|
280
|
+
public Money getBalance() { return balance; }
|
|
281
|
+
public AccountStatus getStatus() { return status; }
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Value Objects
|
|
285
|
+
public record AccountId(String value) {
|
|
286
|
+
public AccountId {
|
|
287
|
+
if (value == null || value.isBlank()) {
|
|
288
|
+
throw new IllegalArgumentException("Account ID cannot be null or blank");
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
public record Money(BigDecimal amount, Currency currency) implements Comparable<Money> {
|
|
294
|
+
public static final Money ZERO = new Money(BigDecimal.ZERO, Currency.USD);
|
|
295
|
+
|
|
296
|
+
public Money add(Money other) {
|
|
297
|
+
validateSameCurrency(other);
|
|
298
|
+
return new Money(this.amount.add(other.amount), this.currency);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
public Money subtract(Money other) {
|
|
302
|
+
validateSameCurrency(other);
|
|
303
|
+
return new Money(this.amount.subtract(other.amount), this.currency);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
@Override
|
|
307
|
+
public int compareTo(Money other) {
|
|
308
|
+
validateSameCurrency(other);
|
|
309
|
+
return this.amount.compareTo(other.amount);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private void validateSameCurrency(Money other) {
|
|
313
|
+
if (!this.currency.equals(other.currency)) {
|
|
314
|
+
throw new IllegalArgumentException("Cannot operate on different currencies");
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
public enum AccountType {
|
|
320
|
+
CHECKING, SAVINGS, CREDIT, LOAN
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
public enum AccountStatus {
|
|
324
|
+
ACTIVE, SUSPENDED, CLOSED, FROZEN
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### 2. Application Layer - Transfer Service
|
|
329
|
+
|
|
330
|
+
**Use Case Orchestration**
|
|
331
|
+
|
|
332
|
+
```java
|
|
333
|
+
// src/main/java/com/bank/application/transfer/TransferService.java
|
|
334
|
+
package com.bank.application.transfer;
|
|
335
|
+
|
|
336
|
+
import com.bank.domain.account.*;
|
|
337
|
+
import com.bank.domain.common.Money;
|
|
338
|
+
import com.bank.infrastructure.events.EventPublisher;
|
|
339
|
+
import com.bank.infrastructure.fraud.FraudDetectionService;
|
|
340
|
+
import org.springframework.stereotype.Service;
|
|
341
|
+
import org.springframework.transaction.annotation.Transactional;
|
|
342
|
+
import java.time.LocalDateTime;
|
|
343
|
+
|
|
344
|
+
@Service
|
|
345
|
+
public class TransferService {
|
|
346
|
+
private final AccountRepository accountRepository;
|
|
347
|
+
private final TransferRepository transferRepository;
|
|
348
|
+
private final FraudDetectionService fraudDetection;
|
|
349
|
+
private final EventPublisher eventPublisher;
|
|
350
|
+
private final AuditLogger auditLogger;
|
|
351
|
+
|
|
352
|
+
public TransferService(
|
|
353
|
+
AccountRepository accountRepository,
|
|
354
|
+
TransferRepository transferRepository,
|
|
355
|
+
FraudDetectionService fraudDetection,
|
|
356
|
+
EventPublisher eventPublisher,
|
|
357
|
+
AuditLogger auditLogger
|
|
358
|
+
) {
|
|
359
|
+
this.accountRepository = accountRepository;
|
|
360
|
+
this.transferRepository = transferRepository;
|
|
361
|
+
this.fraudDetection = fraudDetection;
|
|
362
|
+
this.eventPublisher = eventPublisher;
|
|
363
|
+
this.auditLogger = auditLogger;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
@Transactional
|
|
367
|
+
public TransferResult executeTransfer(TransferRequest request) {
|
|
368
|
+
// 1. Validate request
|
|
369
|
+
validateTransferRequest(request);
|
|
370
|
+
|
|
371
|
+
// 2. Load accounts
|
|
372
|
+
Account sourceAccount = accountRepository.findById(request.sourceAccountId())
|
|
373
|
+
.orElseThrow(() -> new AccountNotFoundException(request.sourceAccountId()));
|
|
374
|
+
|
|
375
|
+
Account targetAccount = accountRepository.findById(request.targetAccountId())
|
|
376
|
+
.orElseThrow(() -> new AccountNotFoundException(request.targetAccountId()));
|
|
377
|
+
|
|
378
|
+
// 3. Fraud detection
|
|
379
|
+
FraudCheckResult fraudCheck = fraudDetection.checkTransfer(
|
|
380
|
+
sourceAccount,
|
|
381
|
+
targetAccount,
|
|
382
|
+
request.amount()
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
if (fraudCheck.isSuspicious()) {
|
|
386
|
+
auditLogger.logSuspiciousActivity(request, fraudCheck);
|
|
387
|
+
throw new SuspiciousActivityException("Transfer flagged for review");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// 4. Execute transfer (domain logic)
|
|
391
|
+
sourceAccount.transfer(targetAccount, request.amount(), request.description());
|
|
392
|
+
|
|
393
|
+
// 5. Save accounts
|
|
394
|
+
accountRepository.save(sourceAccount);
|
|
395
|
+
accountRepository.save(targetAccount);
|
|
396
|
+
|
|
397
|
+
// 6. Create transfer record
|
|
398
|
+
Transfer transfer = Transfer.create(
|
|
399
|
+
sourceAccount.getId(),
|
|
400
|
+
targetAccount.getId(),
|
|
401
|
+
request.amount(),
|
|
402
|
+
request.description(),
|
|
403
|
+
TransferStatus.COMPLETED,
|
|
404
|
+
LocalDateTime.now()
|
|
405
|
+
);
|
|
406
|
+
transferRepository.save(transfer);
|
|
407
|
+
|
|
408
|
+
// 7. Publish event
|
|
409
|
+
eventPublisher.publish(new TransferCompletedEvent(
|
|
410
|
+
transfer.getId(),
|
|
411
|
+
sourceAccount.getId(),
|
|
412
|
+
targetAccount.getId(),
|
|
413
|
+
request.amount(),
|
|
414
|
+
LocalDateTime.now()
|
|
415
|
+
));
|
|
416
|
+
|
|
417
|
+
// 8. Audit log
|
|
418
|
+
auditLogger.logTransfer(request, transfer, "SUCCESS");
|
|
419
|
+
|
|
420
|
+
return new TransferResult(transfer.getId(), TransferStatus.COMPLETED);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private void validateTransferRequest(TransferRequest request) {
|
|
424
|
+
if (request.amount().getAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
|
425
|
+
throw new IllegalArgumentException("Transfer amount must be positive");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (request.sourceAccountId().equals(request.targetAccountId())) {
|
|
429
|
+
throw new IllegalArgumentException("Source and target accounts must be different");
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
### 3. Infrastructure Layer - Repository Implementation
|
|
438
|
+
|
|
439
|
+
**Data Access with JPA**
|
|
440
|
+
|
|
441
|
+
```java
|
|
442
|
+
// src/main/java/com/bank/infrastructure/persistence/JpaAccountRepository.java
|
|
443
|
+
package com.bank.infrastructure.persistence;
|
|
444
|
+
|
|
445
|
+
import com.bank.domain.account.*;
|
|
446
|
+
import org.springframework.data.jpa.repository.JpaRepository;
|
|
447
|
+
import org.springframework.data.jpa.repository.Lock;
|
|
448
|
+
import org.springframework.data.jpa.repository.Query;
|
|
449
|
+
import org.springframework.stereotype.Repository;
|
|
450
|
+
import javax.persistence.LockModeType;
|
|
451
|
+
import java.util.Optional;
|
|
452
|
+
|
|
453
|
+
@Repository
|
|
454
|
+
public interface JpaAccountRepository extends JpaRepository<AccountEntity, String>, AccountRepository {
|
|
455
|
+
|
|
456
|
+
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
|
457
|
+
@Query("SELECT a FROM AccountEntity a WHERE a.id = :id")
|
|
458
|
+
Optional<AccountEntity> findByIdForUpdate(String id);
|
|
459
|
+
|
|
460
|
+
@Query("SELECT a FROM AccountEntity a WHERE a.customerId = :customerId AND a.status = 'ACTIVE'")
|
|
461
|
+
List<AccountEntity> findActiveAccountsByCustomer(String customerId);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Domain Repository Interface
|
|
465
|
+
public interface AccountRepository {
|
|
466
|
+
Optional<Account> findById(AccountId id);
|
|
467
|
+
Account save(Account account);
|
|
468
|
+
void delete(Account account);
|
|
469
|
+
List<Account> findByCustomerId(CustomerId customerId);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Repository Adapter
|
|
473
|
+
@Component
|
|
474
|
+
public class AccountRepositoryAdapter implements AccountRepository {
|
|
475
|
+
private final JpaAccountRepository jpaRepository;
|
|
476
|
+
private final AccountMapper mapper;
|
|
477
|
+
|
|
478
|
+
@Override
|
|
479
|
+
public Optional<Account> findById(AccountId id) {
|
|
480
|
+
return jpaRepository.findByIdForUpdate(id.value())
|
|
481
|
+
.map(mapper::toDomain);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
@Override
|
|
485
|
+
public Account save(Account account) {
|
|
486
|
+
AccountEntity entity = mapper.toEntity(account);
|
|
487
|
+
AccountEntity saved = jpaRepository.save(entity);
|
|
488
|
+
return mapper.toDomain(saved);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// JPA Entity
|
|
493
|
+
@Entity
|
|
494
|
+
@Table(name = "accounts")
|
|
495
|
+
public class AccountEntity {
|
|
496
|
+
@Id
|
|
497
|
+
private String id;
|
|
498
|
+
|
|
499
|
+
@Column(name = "customer_id", nullable = false)
|
|
500
|
+
private String customerId;
|
|
501
|
+
|
|
502
|
+
@Enumerated(EnumType.STRING)
|
|
503
|
+
@Column(nullable = false)
|
|
504
|
+
private AccountType type;
|
|
505
|
+
|
|
506
|
+
@Column(nullable = false, precision = 19, scale = 4)
|
|
507
|
+
private BigDecimal balance;
|
|
508
|
+
|
|
509
|
+
@Enumerated(EnumType.STRING)
|
|
510
|
+
@Column(nullable = false)
|
|
511
|
+
private AccountStatus status;
|
|
512
|
+
|
|
513
|
+
@Column(name = "overdraft_limit", precision = 19, scale = 4)
|
|
514
|
+
private BigDecimal overdraftLimit;
|
|
515
|
+
|
|
516
|
+
@Column(name = "created_at", nullable = false)
|
|
517
|
+
private LocalDateTime createdAt;
|
|
518
|
+
|
|
519
|
+
@Column(name = "closed_at")
|
|
520
|
+
private LocalDateTime closedAt;
|
|
521
|
+
|
|
522
|
+
@Version
|
|
523
|
+
private Long version; // Optimistic locking
|
|
524
|
+
|
|
525
|
+
// Getters and setters
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### 4. Presentation Layer - REST API
|
|
530
|
+
|
|
531
|
+
**Account Controller**
|
|
532
|
+
|
|
533
|
+
```java
|
|
534
|
+
// src/main/java/com/bank/presentation/api/AccountController.java
|
|
535
|
+
package com.bank.presentation.api;
|
|
536
|
+
|
|
537
|
+
import com.bank.application.account.*;
|
|
538
|
+
import com.bank.domain.common.Money;
|
|
539
|
+
import org.springframework.http.ResponseEntity;
|
|
540
|
+
import org.springframework.security.access.prepost.PreAuthorize;
|
|
541
|
+
import org.springframework.web.bind.annotation.*;
|
|
542
|
+
import javax.validation.Valid;
|
|
543
|
+
|
|
544
|
+
@RestController
|
|
545
|
+
@RequestMapping("/api/v1/accounts")
|
|
546
|
+
public class AccountController {
|
|
547
|
+
private final AccountService accountService;
|
|
548
|
+
private final TransferService transferService;
|
|
549
|
+
|
|
550
|
+
@GetMapping("/{accountId}")
|
|
551
|
+
@PreAuthorize("hasPermission(#accountId, 'Account', 'READ')")
|
|
552
|
+
public ResponseEntity<AccountDto> getAccount(@PathVariable String accountId) {
|
|
553
|
+
Account account = accountService.getAccount(new AccountId(accountId));
|
|
554
|
+
return ResponseEntity.ok(AccountDto.from(account));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
@GetMapping("/{accountId}/balance")
|
|
558
|
+
@PreAuthorize("hasPermission(#accountId, 'Account', 'READ')")
|
|
559
|
+
public ResponseEntity<BalanceDto> getBalance(@PathVariable String accountId) {
|
|
560
|
+
Money balance = accountService.getBalance(new AccountId(accountId));
|
|
561
|
+
return ResponseEntity.ok(new BalanceDto(balance.getAmount(), balance.getCurrency()));
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
@PostMapping("/{accountId}/deposit")
|
|
565
|
+
@PreAuthorize("hasPermission(#accountId, 'Account', 'WRITE')")
|
|
566
|
+
public ResponseEntity<TransactionDto> deposit(
|
|
567
|
+
@PathVariable String accountId,
|
|
568
|
+
@Valid @RequestBody DepositRequest request
|
|
569
|
+
) {
|
|
570
|
+
Transaction transaction = accountService.deposit(
|
|
571
|
+
new AccountId(accountId),
|
|
572
|
+
new Money(request.amount(), Currency.valueOf(request.currency())),
|
|
573
|
+
request.description()
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
return ResponseEntity.ok(TransactionDto.from(transaction));
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
@PostMapping("/{accountId}/withdraw")
|
|
580
|
+
@PreAuthorize("hasPermission(#accountId, 'Account', 'WRITE')")
|
|
581
|
+
public ResponseEntity<TransactionDto> withdraw(
|
|
582
|
+
@PathVariable String accountId,
|
|
583
|
+
@Valid @RequestBody WithdrawRequest request
|
|
584
|
+
) {
|
|
585
|
+
Transaction transaction = accountService.withdraw(
|
|
586
|
+
new AccountId(accountId),
|
|
587
|
+
new Money(request.amount(), Currency.valueOf(request.currency())),
|
|
588
|
+
request.description()
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
return ResponseEntity.ok(TransactionDto.from(transaction));
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
@PostMapping("/transfer")
|
|
595
|
+
@PreAuthorize("hasPermission(#request.sourceAccountId, 'Account', 'WRITE')")
|
|
596
|
+
public ResponseEntity<TransferDto> transfer(@Valid @RequestBody TransferRequest request) {
|
|
597
|
+
TransferResult result = transferService.executeTransfer(request);
|
|
598
|
+
return ResponseEntity.ok(TransferDto.from(result));
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
@GetMapping("/{accountId}/transactions")
|
|
602
|
+
@PreAuthorize("hasPermission(#accountId, 'Account', 'READ')")
|
|
603
|
+
public ResponseEntity<Page<TransactionDto>> getTransactions(
|
|
604
|
+
@PathVariable String accountId,
|
|
605
|
+
@RequestParam(defaultValue = "0") int page,
|
|
606
|
+
@RequestParam(defaultValue = "20") int size
|
|
607
|
+
) {
|
|
608
|
+
Page<Transaction> transactions = accountService.getTransactions(
|
|
609
|
+
new AccountId(accountId),
|
|
610
|
+
PageRequest.of(page, size)
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
return ResponseEntity.ok(transactions.map(TransactionDto::from));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// DTOs
|
|
618
|
+
public record AccountDto(
|
|
619
|
+
String id,
|
|
620
|
+
String customerId,
|
|
621
|
+
String type,
|
|
622
|
+
BigDecimal balance,
|
|
623
|
+
String currency,
|
|
624
|
+
String status,
|
|
625
|
+
LocalDateTime createdAt
|
|
626
|
+
) {
|
|
627
|
+
public static AccountDto from(Account account) {
|
|
628
|
+
return new AccountDto(
|
|
629
|
+
account.getId().value(),
|
|
630
|
+
account.getCustomerId().value(),
|
|
631
|
+
account.getType().name(),
|
|
632
|
+
account.getBalance().getAmount(),
|
|
633
|
+
account.getBalance().getCurrency().name(),
|
|
634
|
+
account.getStatus().name(),
|
|
635
|
+
account.getCreatedAt()
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
public record DepositRequest(
|
|
641
|
+
@NotNull @Positive BigDecimal amount,
|
|
642
|
+
@NotNull String currency,
|
|
643
|
+
@NotBlank String description
|
|
644
|
+
) {}
|
|
645
|
+
|
|
646
|
+
public record WithdrawRequest(
|
|
647
|
+
@NotNull @Positive BigDecimal amount,
|
|
648
|
+
@NotNull String currency,
|
|
649
|
+
@NotBlank String description
|
|
650
|
+
) {}
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## Security Implementation
|
|
656
|
+
|
|
657
|
+
### 1. Authentication & Authorization
|
|
658
|
+
|
|
659
|
+
**Spring Security Configuration**
|
|
660
|
+
|
|
661
|
+
```java
|
|
662
|
+
// src/main/java/com/bank/infrastructure/security/SecurityConfig.java
|
|
663
|
+
package com.bank.infrastructure.security;
|
|
664
|
+
|
|
665
|
+
import org.springframework.context.annotation.Bean;
|
|
666
|
+
import org.springframework.context.annotation.Configuration;
|
|
667
|
+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
|
668
|
+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
669
|
+
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
670
|
+
import org.springframework.security.web.SecurityFilterChain;
|
|
671
|
+
|
|
672
|
+
@Configuration
|
|
673
|
+
@EnableMethodSecurity(prePostEnabled = true)
|
|
674
|
+
public class SecurityConfig {
|
|
675
|
+
|
|
676
|
+
@Bean
|
|
677
|
+
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|
678
|
+
http
|
|
679
|
+
.csrf().disable()
|
|
680
|
+
.sessionManagement()
|
|
681
|
+
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
|
682
|
+
.and()
|
|
683
|
+
.authorizeHttpRequests(auth -> auth
|
|
684
|
+
.requestMatchers("/api/v1/public/**").permitAll()
|
|
685
|
+
.requestMatchers("/api/v1/accounts/**").authenticated()
|
|
686
|
+
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
|
|
687
|
+
.anyRequest().authenticated()
|
|
688
|
+
)
|
|
689
|
+
.oauth2ResourceServer()
|
|
690
|
+
.jwt()
|
|
691
|
+
.jwtAuthenticationConverter(jwtAuthenticationConverter());
|
|
692
|
+
|
|
693
|
+
return http.build();
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
@Bean
|
|
697
|
+
public JwtAuthenticationConverter jwtAuthenticationConverter() {
|
|
698
|
+
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
|
|
699
|
+
grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
|
|
700
|
+
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
|
|
701
|
+
|
|
702
|
+
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
|
|
703
|
+
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
|
|
704
|
+
return jwtAuthenticationConverter;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
**Custom Permission Evaluator**
|
|
710
|
+
|
|
711
|
+
```java
|
|
712
|
+
// src/main/java/com/bank/infrastructure/security/AccountPermissionEvaluator.java
|
|
713
|
+
@Component
|
|
714
|
+
public class AccountPermissionEvaluator implements PermissionEvaluator {
|
|
715
|
+
private final AccountRepository accountRepository;
|
|
716
|
+
|
|
717
|
+
@Override
|
|
718
|
+
public boolean hasPermission(
|
|
719
|
+
Authentication authentication,
|
|
720
|
+
Object targetDomainObject,
|
|
721
|
+
Object permission
|
|
722
|
+
) {
|
|
723
|
+
if (authentication == null || !(targetDomainObject instanceof String)) {
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
String accountId = (String) targetDomainObject;
|
|
728
|
+
String userId = authentication.getName();
|
|
729
|
+
|
|
730
|
+
// Check if user owns the account
|
|
731
|
+
Account account = accountRepository.findById(new AccountId(accountId))
|
|
732
|
+
.orElseThrow(() -> new AccountNotFoundException(accountId));
|
|
733
|
+
|
|
734
|
+
return account.getCustomerId().value().equals(userId) ||
|
|
735
|
+
authentication.getAuthorities().stream()
|
|
736
|
+
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
### 2. Audit Logging
|
|
743
|
+
|
|
744
|
+
**Comprehensive Audit Trail**
|
|
745
|
+
|
|
746
|
+
```java
|
|
747
|
+
// src/main/java/com/bank/infrastructure/audit/AuditLogger.java
|
|
748
|
+
@Component
|
|
749
|
+
public class AuditLogger {
|
|
750
|
+
private final AuditRepository auditRepository;
|
|
751
|
+
private final KafkaTemplate<String, AuditEvent> kafkaTemplate;
|
|
752
|
+
|
|
753
|
+
public void logTransfer(TransferRequest request, Transfer transfer, String status) {
|
|
754
|
+
AuditEvent event = AuditEvent.builder()
|
|
755
|
+
.eventType("TRANSFER")
|
|
756
|
+
.userId(SecurityContextHolder.getContext().getAuthentication().getName())
|
|
757
|
+
.entityType("Transfer")
|
|
758
|
+
.entityId(transfer.getId().value())
|
|
759
|
+
.action("EXECUTE")
|
|
760
|
+
.status(status)
|
|
761
|
+
.details(Map.of(
|
|
762
|
+
"sourceAccountId", request.sourceAccountId(),
|
|
763
|
+
"targetAccountId", request.targetAccountId(),
|
|
764
|
+
"amount", request.amount().toString(),
|
|
765
|
+
"description", request.description()
|
|
766
|
+
))
|
|
767
|
+
.ipAddress(getClientIpAddress())
|
|
768
|
+
.timestamp(LocalDateTime.now())
|
|
769
|
+
.build();
|
|
770
|
+
|
|
771
|
+
// Store in database
|
|
772
|
+
auditRepository.save(event);
|
|
773
|
+
|
|
774
|
+
// Publish to Kafka for real-time monitoring
|
|
775
|
+
kafkaTemplate.send("audit-events", event);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
public void logSuspiciousActivity(TransferRequest request, FraudCheckResult fraudCheck) {
|
|
779
|
+
AuditEvent event = AuditEvent.builder()
|
|
780
|
+
.eventType("FRAUD_ALERT")
|
|
781
|
+
.userId(SecurityContextHolder.getContext().getAuthentication().getName())
|
|
782
|
+
.action("TRANSFER_BLOCKED")
|
|
783
|
+
.status("SUSPICIOUS")
|
|
784
|
+
.details(Map.of(
|
|
785
|
+
"reason", fraudCheck.getReason(),
|
|
786
|
+
"riskScore", fraudCheck.getRiskScore(),
|
|
787
|
+
"amount", request.amount().toString()
|
|
788
|
+
))
|
|
789
|
+
.timestamp(LocalDateTime.now())
|
|
790
|
+
.build();
|
|
791
|
+
|
|
792
|
+
auditRepository.save(event);
|
|
793
|
+
kafkaTemplate.send("fraud-alerts", event);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
### 3. Data Encryption
|
|
799
|
+
|
|
800
|
+
**Encryption at Rest and in Transit**
|
|
801
|
+
|
|
802
|
+
```java
|
|
803
|
+
// src/main/java/com/bank/infrastructure/security/EncryptionService.java
|
|
804
|
+
@Service
|
|
805
|
+
public class EncryptionService {
|
|
806
|
+
private final KeyManagementService kms;
|
|
807
|
+
|
|
808
|
+
public String encryptSensitiveData(String plaintext) {
|
|
809
|
+
try {
|
|
810
|
+
// Use AWS KMS or HSM for key management
|
|
811
|
+
byte[] dataKey = kms.generateDataKey();
|
|
812
|
+
|
|
813
|
+
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
|
814
|
+
GCMParameterSpec spec = new GCMParameterSpec(128, generateIV());
|
|
815
|
+
SecretKeySpec keySpec = new SecretKeySpec(dataKey, "AES");
|
|
816
|
+
|
|
817
|
+
cipher.init(Cipher.ENCRYPT_MODE, keySpec, spec);
|
|
818
|
+
byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
|
|
819
|
+
|
|
820
|
+
return Base64.getEncoder().encodeToString(encrypted);
|
|
821
|
+
} catch (Exception e) {
|
|
822
|
+
throw new EncryptionException("Failed to encrypt data", e);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
public String decryptSensitiveData(String ciphertext) {
|
|
827
|
+
// Decryption logic
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
---
|
|
833
|
+
|
|
834
|
+
## Compliance & Regulatory
|
|
835
|
+
|
|
836
|
+
### 1. SOX Compliance
|
|
837
|
+
|
|
838
|
+
**Segregation of Duties**
|
|
839
|
+
|
|
840
|
+
```java
|
|
841
|
+
// src/main/java/com/bank/infrastructure/compliance/SoxComplianceService.java
|
|
842
|
+
@Service
|
|
843
|
+
public class SoxComplianceService {
|
|
844
|
+
|
|
845
|
+
@PreAuthorize("hasRole('MAKER')")
|
|
846
|
+
public void createTransaction(TransactionRequest request) {
|
|
847
|
+
// Maker creates transaction in PENDING state
|
|
848
|
+
Transaction transaction = Transaction.createPending(request);
|
|
849
|
+
transactionRepository.save(transaction);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
@PreAuthorize("hasRole('CHECKER') and !hasRole('MAKER')")
|
|
853
|
+
public void approveTransaction(String transactionId) {
|
|
854
|
+
// Checker approves (different person than maker)
|
|
855
|
+
Transaction transaction = transactionRepository.findById(transactionId);
|
|
856
|
+
|
|
857
|
+
if (transaction.getCreatedBy().equals(getCurrentUser())) {
|
|
858
|
+
throw new SoxViolationException("Cannot approve own transaction");
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
transaction.approve(getCurrentUser());
|
|
862
|
+
transactionRepository.save(transaction);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
### 2. GDPR Compliance
|
|
868
|
+
|
|
869
|
+
**Data Privacy and Right to be Forgotten**
|
|
870
|
+
|
|
871
|
+
```java
|
|
872
|
+
// src/main/java/com/bank/application/gdpr/GdprService.java
|
|
873
|
+
@Service
|
|
874
|
+
public class GdprService {
|
|
875
|
+
|
|
876
|
+
public CustomerDataExport exportCustomerData(CustomerId customerId) {
|
|
877
|
+
// Export all customer data
|
|
878
|
+
Customer customer = customerRepository.findById(customerId);
|
|
879
|
+
List<Account> accounts = accountRepository.findByCustomerId(customerId);
|
|
880
|
+
List<Transaction> transactions = transactionRepository.findByCustomerId(customerId);
|
|
881
|
+
|
|
882
|
+
return CustomerDataExport.builder()
|
|
883
|
+
.customer(customer)
|
|
884
|
+
.accounts(accounts)
|
|
885
|
+
.transactions(transactions)
|
|
886
|
+
.exportDate(LocalDateTime.now())
|
|
887
|
+
.build();
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
@Transactional
|
|
891
|
+
public void anonymizeCustomerData(CustomerId customerId) {
|
|
892
|
+
// Anonymize customer data (cannot delete due to regulatory requirements)
|
|
893
|
+
Customer customer = customerRepository.findById(customerId);
|
|
894
|
+
customer.anonymize(); // Replace PII with anonymized values
|
|
895
|
+
|
|
896
|
+
customerRepository.save(customer);
|
|
897
|
+
|
|
898
|
+
auditLogger.log("GDPR_ANONYMIZATION", customerId);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
---
|
|
904
|
+
|
|
905
|
+
## Key Takeaways
|
|
906
|
+
|
|
907
|
+
### Architecture Decisions
|
|
908
|
+
|
|
909
|
+
1. **Layered Architecture**: Clear separation of concerns, easier to understand and maintain
|
|
910
|
+
2. **Rich Domain Model**: Business logic in domain layer, not anemic models
|
|
911
|
+
3. **Repository Pattern**: Abstracts data access, enables testing
|
|
912
|
+
4. **ACID Transactions**: PostgreSQL ensures data integrity
|
|
913
|
+
5. **Pessimistic Locking**: Prevents concurrent modification of accounts
|
|
914
|
+
6. **Audit Logging**: Complete trail for compliance
|
|
915
|
+
7. **Encryption**: AES-256 for sensitive data
|
|
916
|
+
|
|
917
|
+
### Trade-offs
|
|
918
|
+
|
|
919
|
+
**Benefits**
|
|
920
|
+
- ✅ Clear separation of concerns
|
|
921
|
+
- ✅ Easy to understand and maintain
|
|
922
|
+
- ✅ Strong data consistency (ACID)
|
|
923
|
+
- ✅ Comprehensive security
|
|
924
|
+
- ✅ Regulatory compliance
|
|
925
|
+
- ✅ Testable layers
|
|
926
|
+
|
|
927
|
+
**Challenges**
|
|
928
|
+
- ❌ Monolithic deployment (all layers together)
|
|
929
|
+
- ❌ Vertical scaling only
|
|
930
|
+
- ❌ Technology lock-in (Java/Spring)
|
|
931
|
+
- ❌ Layer overhead for simple operations
|
|
932
|
+
- ❌ Tight coupling within monolith
|
|
933
|
+
|
|
934
|
+
### Security Measures
|
|
935
|
+
|
|
936
|
+
- **Authentication**: OAuth 2.0, JWT tokens
|
|
937
|
+
- **Authorization**: Role-based access control (RBAC)
|
|
938
|
+
- **Encryption**: AES-256 at rest, TLS 1.3 in transit
|
|
939
|
+
- **MFA**: TOTP, SMS, biometric
|
|
940
|
+
- **Audit**: Complete audit trail
|
|
941
|
+
- **Compliance**: SOX, PCI DSS, GDPR, Basel III
|
|
942
|
+
|
|
943
|
+
### Performance Metrics
|
|
944
|
+
|
|
945
|
+
- **Transaction Processing**: < 500ms (P95)
|
|
946
|
+
- **API Response Time**: < 200ms (P95)
|
|
947
|
+
- **Database Queries**: < 100ms (P95)
|
|
948
|
+
- **Availability**: 99.95%
|
|
949
|
+
- **Throughput**: 100,000 transactions/day
|
|
950
|
+
|
|
951
|
+
---
|
|
952
|
+
|
|
953
|
+
## References
|
|
954
|
+
|
|
955
|
+
- [Layered Architecture](../rules/layered-architecture.md)
|
|
956
|
+
- [Security Architecture](../rules/security.md)
|
|
957
|
+
- [Domain-Driven Design](../rules/tools-methodologies.md)
|
|
958
|
+
- [Quality Attributes](../rules/quality-attributes.md)
|
|
959
|
+
- [Spring Security Documentation](https://spring.io/projects/spring-security)
|
|
960
|
+
- [PCI DSS Compliance](https://www.pcisecuritystandards.org/)
|
|
961
|
+
|