-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathBankingExamples.cs
More file actions
330 lines (276 loc) · 12.4 KB
/
BankingExamples.cs
File metadata and controls
330 lines (276 loc) · 12.4 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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
using BankingExample.Aggregates;
using BankingExample.Services;
using BankingExample.ValueObjects;
using BankingExample.Workflows;
using Trellis;
using Trellis.Primitives;
namespace BankingExample;
/// <summary>
/// Demonstrates banking operations with Railway Oriented Programming and Domain-Driven Design.
///
/// Features demonstrated:
/// - Result<T> and ROP patterns (Bind, Map, Ensure, Tap, Match)
/// - Aggregates with domain events
/// - Change tracking with UncommittedEvents and AcceptChanges
/// - Various error types (Validation, Domain, Conflict, Unauthorized)
/// - Async workflows with ParallelAsync and RecoverOnFailureAsync
/// - Value objects with validation
/// </summary>
public static class BankingExamples
{
public static async Task RunExamplesAsync()
{
Console.WriteLine("=== Banking Transaction Examples ===");
Console.WriteLine("=== Demonstrating FunctionalDDD Library ===\n");
await Example1_BasicAccountOperationsWithEvents();
await Example2_TransferBetweenAccountsWithEvents();
await Example3_FraudDetection();
await Example4_DailyWithdrawalLimit();
await Example5_InterestPayment();
await Example6_DomainEventsAndChangeTracking();
}
/// <summary>
/// Example 1: Basic account operations with domain events.
/// Demonstrates: Aggregate creation, domain events, ROP chaining
/// </summary>
private static async Task Example1_BasicAccountOperationsWithEvents()
{
Console.WriteLine("Example 1: Basic Account Operations with Domain Events");
Console.WriteLine("-------------------------------------------------------");
var customerId = CustomerId.NewUniqueV4();
var result = BankAccount.TryCreate(
customerId,
AccountType.Checking,
Money.Create(1000m, "USD"),
Money.Create(500m, "USD"),
Money.Create(100m, "USD"))
.Tap(account =>
{
// Show that account creation raised an event
Console.WriteLine($"Account created with {account.UncommittedEvents().Count} uncommitted event(s)");
})
.Bind(account => account.Deposit(Money.Create(500m, "USD"), "Salary deposit"))
.Bind(account => account.Withdraw(Money.Create(200m, "USD"), "Grocery shopping"))
.Tap(account =>
{
// Show accumulated events before accepting changes
Console.WriteLine($"After operations: {account.UncommittedEvents().Count} uncommitted event(s)");
Console.WriteLine($"IsChanged: {account.IsChanged}");
// Simulate repository save - accept changes
account.AcceptChanges();
Console.WriteLine($"After AcceptChanges: {account.UncommittedEvents().Count} uncommitted event(s)");
Console.WriteLine($"IsChanged: {account.IsChanged}");
})
.Match(
onSuccess: ok => $"✅ Account balance: {ok.Balance}. Transactions: {ok.Transactions.Count}",
onFailure: err => $"❌ Operation failed: {err.Detail}"
);
Console.WriteLine(result);
Console.WriteLine();
await Task.CompletedTask;
}
/// <summary>
/// Example 2: Transfer money between accounts with domain event publishing.
/// Demonstrates: Workflow orchestration, event publishing pattern
/// </summary>
private static async Task Example2_TransferBetweenAccountsWithEvents()
{
Console.WriteLine("Example 2: Transfer Between Accounts with Event Publishing");
Console.WriteLine("-----------------------------------------------------------");
var customer1 = CustomerId.NewUniqueV4();
var customer2 = CustomerId.NewUniqueV4();
var account1 = BankAccount.TryCreate(
customer1,
AccountType.Checking,
Money.Create(1000m, "USD"),
Money.Create(500m, "USD"),
Money.Create(0m, "USD")
).Value;
var account2 = BankAccount.TryCreate(
customer2,
AccountType.Savings,
Money.Create(500m, "USD"),
Money.Create(500m, "USD"),
Money.Create(0m, "USD")
).Value;
// Clear initial creation events for cleaner demo output
account1.AcceptChanges();
account2.AcceptChanges();
var fraudDetection = new FraudDetectionService();
var workflow = new BankingWorkflow(fraudDetection);
var result = await workflow.ProcessTransferAsync(
account1,
account2,
Money.Create(300m, "USD"),
"Rent payment"
);
var message = result.Match(
onSuccess: ok => $"✅ Transfer successful!\n From account balance: {ok.From.Balance}\n To account balance: {ok.To.Balance}",
onFailure: err => $"❌ Transfer failed: {err.Detail}"
);
Console.WriteLine(message);
Console.WriteLine();
}
/// <summary>
/// Example 3: Fraud detection with various error types.
/// Demonstrates: Error.Domain with custom codes, RecoverOnFailureAsync
/// </summary>
private static async Task Example3_FraudDetection()
{
Console.WriteLine("Example 3: Fraud Detection with Error Types");
Console.WriteLine("--------------------------------------------");
var customerId = CustomerId.NewUniqueV4();
var account = BankAccount.TryCreate(
customerId,
AccountType.Checking,
Money.Create(10000m, "USD"),
Money.Create(10000m, "USD"),
Money.Create(0m, "USD")
).Value;
account.AcceptChanges(); // Clear creation event
var fraudDetection = new FraudDetectionService();
var workflow = new BankingWorkflow(fraudDetection);
// Try to withdraw a suspicious amount (>$5000)
var result = await workflow.ProcessSecureWithdrawalAsync(
account,
Money.Create(6000m, "USD"),
"123456" // MFA code
);
var message = result.Match(
onSuccess: ok => $"✅ Withdrawal successful. New balance: {ok.Balance}",
onFailure: err => $"⚠️ Expected fraud detection:\n Code: {err.Code}\n Detail: {err.Detail}"
);
Console.WriteLine(message);
Console.WriteLine();
}
/// <summary>
/// Example 4: Daily withdrawal limit enforcement.
/// Demonstrates: Error.Domain for business rule violations
/// </summary>
private static async Task Example4_DailyWithdrawalLimit()
{
Console.WriteLine("Example 4: Daily Withdrawal Limit (Domain Errors)");
Console.WriteLine("--------------------------------------------------");
var customerId = CustomerId.NewUniqueV4();
var dailyLimit = Money.Create(500m, "USD");
var account = BankAccount.TryCreate(
customerId,
AccountType.Checking,
Money.Create(2000m, "USD"),
dailyLimit,
Money.Create(0m, "USD")
).Value;
account.AcceptChanges(); // Clear creation event
// Make multiple withdrawals
var result1 = account.Withdraw(Money.Create(200m, "USD"), "ATM withdrawal");
Console.WriteLine(result1.IsSuccess ? "✅ First withdrawal: $200" : $"❌ {result1.Error.Detail}");
var result2 = account.Withdraw(Money.Create(200m, "USD"), "ATM withdrawal");
Console.WriteLine(result2.IsSuccess ? "✅ Second withdrawal: $200" : $"❌ {result2.Error.Detail}");
// This should exceed daily limit - demonstrates Error.Domain
var result3 = account.Withdraw(Money.Create(200m, "USD"), "ATM withdrawal");
if (result3.IsFailure)
{
Console.WriteLine($"⚠️ Third withdrawal blocked:");
Console.WriteLine($" Error Type: {result3.Error.GetType().Name}");
Console.WriteLine($" Code: {result3.Error.Code}");
Console.WriteLine($" Detail: {result3.Error.Detail}");
}
Console.WriteLine($"Final balance: {account.Balance}");
Console.WriteLine($"Uncommitted events: {account.UncommittedEvents().Count}");
Console.WriteLine();
await Task.CompletedTask;
}
/// <summary>
/// Example 5: Interest payment with domain events.
/// Demonstrates: Domain-specific events, workflow with event publishing
/// </summary>
private static async Task Example5_InterestPayment()
{
Console.WriteLine("Example 5: Interest Payment with Domain Events");
Console.WriteLine("-----------------------------------------------");
var customerId = CustomerId.NewUniqueV4();
var account = BankAccount.TryCreate(
customerId,
AccountType.Savings,
Money.Create(10000m, "USD"),
Money.Create(100m, "USD"),
Money.Create(0m, "USD")
).Value;
account.AcceptChanges(); // Clear creation event
var fraudDetection = new FraudDetectionService();
var workflow = new BankingWorkflow(fraudDetection);
var annualRate = 0.025m; // 2.5% APR
var result = await workflow.ProcessInterestPaymentAsync(account, annualRate);
var message = result.Match(
onSuccess: ok =>
{
var interestTransaction = ok.Transactions.Last();
return $"✅ Interest payment processed\n" +
$" Amount: {interestTransaction.Amount}\n" +
$" New balance: {ok.Balance}\n" +
$" Annual rate: {annualRate:P2}";
},
onFailure: err => $"❌ Interest payment failed: {err.Detail}"
);
Console.WriteLine(message);
Console.WriteLine();
}
/// <summary>
/// Example 6: Deep dive into domain events and change tracking.
/// Demonstrates: UncommittedEvents, AcceptChanges, IsChanged, event inspection
/// </summary>
private static async Task Example6_DomainEventsAndChangeTracking()
{
Console.WriteLine("Example 6: Domain Events and Change Tracking");
Console.WriteLine("---------------------------------------------");
var customerId = CustomerId.NewUniqueV4();
// Create account - this raises AccountOpenedEvent
var accountResult = BankAccount.TryCreate(
customerId,
AccountType.Checking,
Money.Create(1000m, "USD"),
Money.Create(1000m, "USD"),
Money.Create(100m, "USD")
);
if (accountResult.IsFailure)
{
Console.WriteLine($"❌ Account creation failed: {accountResult.Error.Detail}");
return;
}
var account = accountResult.Value;
Console.WriteLine("After account creation:");
Console.WriteLine($" IsChanged: {account.IsChanged}");
Console.WriteLine($" Uncommitted events: {account.UncommittedEvents().Count}");
PrintEvents(account);
// Perform operations
account.Deposit(Money.Create(500m, "USD"), "Deposit 1");
account.Deposit(Money.Create(300m, "USD"), "Deposit 2");
account.Withdraw(Money.Create(100m, "USD"), "Withdrawal 1");
Console.WriteLine("\nAfter multiple operations:");
Console.WriteLine($" IsChanged: {account.IsChanged}");
Console.WriteLine($" Uncommitted events: {account.UncommittedEvents().Count}");
PrintEvents(account);
// Simulate saving to repository - accept changes
Console.WriteLine("\nSimulating repository save (AcceptChanges)...");
account.AcceptChanges();
Console.WriteLine("\nAfter AcceptChanges:");
Console.WriteLine($" IsChanged: {account.IsChanged}");
Console.WriteLine($" Uncommitted events: {account.UncommittedEvents().Count}");
// New operation after save
account.Deposit(Money.Create(50m, "USD"), "New deposit after save");
Console.WriteLine("\nAfter new operation:");
Console.WriteLine($" IsChanged: {account.IsChanged}");
Console.WriteLine($" Uncommitted events: {account.UncommittedEvents().Count}");
PrintEvents(account);
Console.WriteLine();
await Task.CompletedTask;
}
private static void PrintEvents(BankAccount account)
{
var events = account.UncommittedEvents();
foreach (var evt in events)
{
Console.WriteLine($" - {evt.GetType().Name} at {evt.OccurredAt:HH:mm:ss.fff}");
}
}
}