Domain Layer — Aggregates, Entities & Value Objects
«aggregate root»
Portfolio
id: PortfolioId
clientId: ClientId
holdings: List<Holding>
transactions: List<Transaction>
summary: PortfolioSummary
tier: Tier
createdAt: Instant
addHolding(h: Holding): void
recordTransaction(t: Transaction): void
recalculateSummary(): PortfolioSummary
getXIRR(): XIRR
getAssetAllocation(): Map
«entity»
Holding
id: HoldingId
isin: ISIN
assetClass: AssetClass
units: BigDecimal
avgCost: Money
currentNav: NAV
currentValue: Money
gainOrLoss(): Money
returnPct(): BigDecimal
updateNav(nav: NAV): void
«entity»
Transaction
id: TransactionId
holdingId: HoldingId
type: BUY | SELL | SIP | SWP
amount: Money
units: BigDecimal
nav: NAV
executedAt: Instant
orderId: OrderId?
isCredit(): boolean
stampCost(): Money
«use case»
GetPortfolioSummary
portfolioRepo: PortfolioRepository
fundDataPort: FundDataPort
execute(query): PortfolioSummaryDTO
«use case»
RecordTransaction
portfolioRepo: PortfolioRepository
eventPublisher: EventPublisherPort
execute(cmd: RecordTxnCmd): void
«adapter»
RedisPortfolioCache
redisTemplate: RedisTemplate
ttl: Duration = 5min
getCachedSummary(id): PortfolioSummary?
cacheSummary(id, s): void
«rest controller»
PortfolioController
GET /api/v1/portfolio/{id}
GET /api/v1/portfolio/{id}/summary
POST /api/v1/portfolio/{id}/holdings
POST /api/v1/portfolio/{id}/transactions
POST /api/v1/portfolio/import-aa
Money Value Object — Deep Dive
public record Money(long paise, Currency currency) {
public static Money of(long rupees, int paise) {
return new Money(rupees * 100 + paise, INR);
}
public Money add(Money other) {
require(currency == other.currency);
return new Money(paise + other.paise, currency);
}
public String toString() {
return "₹" + formatIndian(paise / 100)
+ "." + pad2(paise % 100);
}
}