feat: add anemic model detection and refactor hardcoded values (v0.9.0)

This commit is contained in:
imfozilbek
2025-11-26 00:09:48 +05:00
parent 1d6c2a0e00
commit a6b4c69b75
21 changed files with 1481 additions and 4 deletions

View File

@@ -0,0 +1,139 @@
/**
* 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 }

View File

@@ -0,0 +1,104 @@
/**
* GOOD EXAMPLE: Rich Domain Model
*
* This Order class contains business logic and enforces business rules.
* State changes are made through business methods, not setters.
*
* This follows Domain-Driven Design principles.
*/
type OrderStatus = "pending" | "approved" | "rejected" | "shipped"
interface OrderItem {
productId: string
quantity: number
price: number
}
interface DomainEvent {
type: string
data: any
}
class Order {
private readonly id: string
private status: OrderStatus
private items: OrderItem[]
private readonly events: DomainEvent[] = []
constructor(id: string, items: OrderItem[]) {
this.id = id
this.status = "pending"
this.items = items
}
public approve(): void {
if (!this.canBeApproved()) {
throw new Error("Cannot approve order in current state")
}
this.status = "approved"
this.events.push({
type: "OrderApproved",
data: { orderId: this.id },
})
}
public reject(reason: string): void {
if (!this.canBeRejected()) {
throw new Error("Cannot reject order in current state")
}
this.status = "rejected"
this.events.push({
type: "OrderRejected",
data: { orderId: this.id, reason },
})
}
public ship(): void {
if (!this.canBeShipped()) {
throw new Error("Order must be approved before shipping")
}
this.status = "shipped"
this.events.push({
type: "OrderShipped",
data: { orderId: this.id },
})
}
public addItem(item: OrderItem): void {
if (this.status !== "pending") {
throw new Error("Cannot modify approved or shipped order")
}
this.items.push(item)
}
public calculateTotal(): number {
return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
}
public getStatus(): OrderStatus {
return this.status
}
public getItems(): OrderItem[] {
return [...this.items]
}
public getEvents(): DomainEvent[] {
return [...this.events]
}
private canBeApproved(): boolean {
return this.status === "pending" && this.items.length > 0
}
private canBeRejected(): boolean {
return this.status === "pending"
}
private canBeShipped(): boolean {
return this.status === "approved"
}
}
export { Order }