Files
puaros/packages/guardian/examples/good-architecture/domain/entities/Customer.ts

140 lines
3.6 KiB
TypeScript

/**
* GOOD EXAMPLE: Rich Domain Model with Business Logic
*
* This Customer class encapsulates business rules and state transitions.
* No public setters - all changes go through business methods.
*
* This follows Domain-Driven Design and encapsulation principles.
*/
interface Address {
street: string
city: string
country: string
postalCode: string
}
interface DomainEvent {
type: string
data: any
}
class Customer {
private readonly id: string
private email: string
private isActive: boolean
private loyaltyPoints: number
private address: Address | null
private readonly events: DomainEvent[] = []
constructor(id: string, email: string) {
this.id = id
this.email = email
this.isActive = true
this.loyaltyPoints = 0
this.address = null
}
public activate(): void {
if (this.isActive) {
throw new Error("Customer is already active")
}
this.isActive = true
this.events.push({
type: "CustomerActivated",
data: { customerId: this.id },
})
}
public deactivate(reason: string): void {
if (!this.isActive) {
throw new Error("Customer is already inactive")
}
this.isActive = false
this.events.push({
type: "CustomerDeactivated",
data: { customerId: this.id, reason },
})
}
public changeEmail(newEmail: string): void {
if (!this.isValidEmail(newEmail)) {
throw new Error("Invalid email format")
}
if (this.email === newEmail) {
return
}
const oldEmail = this.email
this.email = newEmail
this.events.push({
type: "EmailChanged",
data: { customerId: this.id, oldEmail, newEmail },
})
}
public updateAddress(address: Address): void {
if (!this.isValidAddress(address)) {
throw new Error("Invalid address")
}
this.address = address
this.events.push({
type: "AddressUpdated",
data: { customerId: this.id },
})
}
public addLoyaltyPoints(points: number): void {
if (points <= 0) {
throw new Error("Points must be positive")
}
if (!this.isActive) {
throw new Error("Cannot add points to inactive customer")
}
this.loyaltyPoints += points
this.events.push({
type: "LoyaltyPointsAdded",
data: { customerId: this.id, points },
})
}
public redeemLoyaltyPoints(points: number): void {
if (points <= 0) {
throw new Error("Points must be positive")
}
if (this.loyaltyPoints < points) {
throw new Error("Insufficient loyalty points")
}
this.loyaltyPoints -= points
this.events.push({
type: "LoyaltyPointsRedeemed",
data: { customerId: this.id, points },
})
}
public getEmail(): string {
return this.email
}
public getLoyaltyPoints(): number {
return this.loyaltyPoints
}
public getAddress(): Address | null {
return this.address ? { ...this.address } : null
}
public getEvents(): DomainEvent[] {
return [...this.events]
}
private isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
private isValidAddress(address: Address): boolean {
return !!address.street && !!address.city && !!address.country && !!address.postalCode
}
}
export { Customer }