The Payment Microservice provides a robust and scalable solution for handling financial operations such as User Wallet Management, transaction processing, and balance tracking. Built on Clean Architecture principles, this service ensures maintainability and flexibility, enabling seamless integration into various systems.
The architecture adheres to Clean Architecture principles, structured in layers:
- Payment Application: Manages request processing, repository definitions, and interaction with external services.
- Payment Domain: Follows Domain Driven Design (DDD) with entities like
UserandWallet, applying business rules and logic. - Payment Infrastructure: Contains infrastructural code, including repositories for databases and external components.
.
│
├─ Payment
│ ├─ Payment.Application
│ ├─ Payment.Domain
│ ├─ Payment.Infrastructure
│ └─ Payment.Application.Tests
│
└─ README.mdThe Payment Application layer, also known as the outermost or edge layer, handles interactions with clients and is responsible for processing requests. It contains implementation details for any repository used in the application.
The Payment Domain is the heart of the system and focuses on business logic. Key principles applied to the Domain Models include:
- Encapsulation: Members are private by default, promoting data integrity.
- Persistence Ignorance: Domain models are POCOs, with no infrastructure dependencies.
- Rich Behavior: Business logic is contained within domain models, ensuring a clear separation of concerns.
- Value Objects: Group related primitive attributes into Value Objects to reduce primitive obsession.
- Business Language: All naming conventions in the domain reflect the language used in the business context.
- Testability: The domain is isolated and easy to test.
public sealed record Wallet : BaseEntity<WalletId>, IComparable<Wallet>, IComparable
{
private readonly HashSet<Share> _shares = new();
public required UserId OwnerId { get; init; }
public required Money Amount { get; init; }
public ImmutableHashSet<Share> Shares => _shares.ToImmutableHashSet();
public static readonly Wallet NotFound = Create(WalletId.Create(Guid.Empty), UserId.Create(Guid.Empty));
private Wallet(WalletId walletId) : base(walletId) { }
public static Wallet Create(WalletId walletId, UserId ownerId, Money? amount = null)
{
Guard.Against.Null(walletId, nameof(walletId), "WalletId cannot be null.");
Guard.Against.Null(ownerId, nameof(ownerId), "OwnerId cannot be null.");
return new Wallet(walletId)
{
OwnerId = ownerId,
Amount = amount ?? Money.Empty(),
};
}
public void Contribute(Money amount, UserId contributorId)
{
Guard.Against.Negative(amount.InMajorUnits);
Guard.Against.Negative(amount.InMinorUnits);
CheckPolicy(new NoSelfContributionPolicy(OwnerId, contributorId));
CheckPolicy(new SameCurrencyContributionPolicy(Amount.Currency, amount.Currency));
if (_shares.ContributorHasShares(Id, contributorId, amount))
{
AppendShare(amount, contributorId);
}
else
{
AddShare(amount, contributorId);
}
}
private void AppendShare(Money amount, UserId contributorId) => _shares.Append(Id, contributorId, amount);
private void AddShare(Money amount, UserId contributorId)
{
var shareId = ShareId.Create(Guid.NewGuid());
var share = Share.Create(shareId, Id, contributorId, amount);
_shares.Add(share);
}
}In the Payment Domain, we use a T4 template to automatically generate code for handling ISO4217 currency representations. This process ensures that our currency-related code is always up-to-date and synchronized with the ISO4217 specification, minimizing manual errors.
The T4 template automates code generation for currency-related operations (e.g., enumerations, lookup tables). It processes data such as three-letter codes, numeric codes, and currency names, producing C# code for easy integration.
To regenerate the currency-related code, execute the T4 template transformation via Visual Studio's "Run Custom Tool" or Rider's "Run Template" feature.
public static readonly Currency EUR = Currency.Create(
"Euro", "EUR", 978, 2,
new []{ "AUSTRIA", "BELGIUM", "FINLAND", "FRANCE", "GERMANY" });The Payment Infrastructure layer provides two key repositories:
- InMemoryUserRepository
- InMemoryWalletRepository
These ensure efficient data storage and retrieval.
This project ensures robust unit testing using mocking to simulate various scenarios and test the application in isolation.
To move, to breathe, to fly, to float
To gain all while you give
To roam the roads of lands remote
To travel is to live.
Do anything, but let it produce joy.