In DDD (Domain-Driven Design), aggregates are a crucial part of the system. Transactions are often emphasized when discussing aggregates, but in reality, “invariants” are more important.
What Are Invariants?
Invariants are conditions or properties that an object must always satisfy. This is different from “immutable objects” – invariants are rules (invariant) that must be upheld even when the object’s state changes.
For example, a bank account balance must never go negative.
Aggregates and Invariants
Aggregates should be designed to maintain invariants. When using TypeScript, you can check invariants using zod’s refine.
import { z } from 'zod';
// Define the BankAccount schema
const BankAccountSchema = z.object({
id: z.string(),
balance: z.number(),
}).refine(data => data.balance >= 0, {
message: "Balance must be non-negative",
});
// Define the BankAccount type
type BankAccount = z.infer<typeof BankAccountSchema>;
Transactions and Invariants
If transactions are performed in aggregate units where invariants are always guaranteed to be maintained, invariants will always be preserved.
The Role of Repositories
Repositories manage the persistence and retrieval of aggregates. By using zod schemas for validation, invariants can be reliably maintained.
class BankAccountRepository {
private storage: Map<string, BankAccount> = new Map();
save(account: BankAccount): void {
// Validate with zod schema
BankAccountSchema.parse(account);
this.storage.set(account.id, account);
}
findById(id: string): BankAccount | undefined {
return this.storage.get(id);
}
}
Usage Example
A concrete example of managing bank accounts using the repository.
const repository = new BankAccountRepository();
const account: BankAccount = { id: "123", balance: 100 };
repository.save(account);
const retrievedAccount = repository.findById("123");
console.log(retrievedAccount); // { id: "123", balance: 100 }
// Example of violating the invariant
const invalidAccount: BankAccount = { id: "456", balance: -50 };
try {
repository.save(invalidAccount);
} catch (error) {
console.error(error.errors[0].message); // "Balance must be non-negative"
}
Summary
In DDD, it is important to define invariants and perform persistence only in aggregate units, ensuring that invariants are always maintained throughout the object’s lifecycle.