Domain Model
Domain model lengkap dengan entities, value objects, aggregates, dan business rules.🏗️ Domain-Driven Design
Aplikasi ini menggunakan prinsip Domain-Driven Design (DDD) untuk memisahkan business logic dari infrastructure.📦 Core Entities
User Aggregate
Copy
// Domain Entity
type User struct {
ID uuid.UUID
Email Email // Value Object
Password Password // Value Object
Name string
Phone Phone // Value Object
Role Role // Value Object
EmailVerified bool
CreatedAt time.Time
UpdatedAt time.Time
}
// Value Objects
type Email struct {
value string
}
func NewEmail(email string) (Email, error) {
if !isValidEmail(email) {
return Email{}, errors.New("invalid email format")
}
return Email{value: strings.ToLower(email)}, nil
}
type Role struct {
name string
}
const (
RoleAdmin Role = Role{"admin"}
RoleUser Role = Role{"user"}
RoleGuest Role = Role{"guest"}
)
Product Aggregate
Copy
type Product struct {
ID uuid.UUID
SKU SKU // Value Object
Name string
Description string
Price Money // Value Object
Stock Stock // Value Object
Category *Category
Active bool
CreatedAt time.Time
}
type Money struct {
Amount decimal.Decimal
Currency string
}
type Stock struct {
Available int
Reserved int
}
func (s *Stock) Reserve(quantity int) error {
if s.Available < quantity {
return errors.New("insufficient stock")
}
s.Available -= quantity
s.Reserved += quantity
return nil
}
Order Aggregate
Copy
type Order struct {
ID uuid.UUID
OrderNumber OrderNumber // Value Object
UserID uuid.UUID
Items []OrderItem
Status OrderStatus // Value Object
TotalAmount Money
CreatedAt time.Time
}
type OrderItem struct {
ProductID uuid.UUID
Quantity int
UnitPrice Money
Subtotal Money
}
type OrderStatus string
const (
OrderStatusPending OrderStatus = "pending"
OrderStatusPaid OrderStatus = "paid"
OrderStatusShipped OrderStatus = "shipped"
OrderStatusCompleted OrderStatus = "completed"
OrderStatusCancelled OrderStatus = "cancelled"
)
// Domain Methods
func (o *Order) CalculateTotal() {
total := decimal.Zero
for _, item := range o.Items {
total = total.Add(item.Subtotal.Amount)
}
o.TotalAmount = Money{Amount: total, Currency: "IDR"}
}
func (o *Order) CanBeCancelled() bool {
return o.Status == OrderStatusPending || o.Status == OrderStatusPaid
}
🎯 Aggregates & Boundaries
📚 Repository Interfaces
Copy
// Domain Repository Interface
type UserRepository interface {
FindByID(ctx context.Context, id uuid.UUID) (*User, error)
FindByEmail(ctx context.Context, email Email) (*User, error)
Save(ctx context.Context, user *User) error
Delete(ctx context.Context, id uuid.UUID) error
}
type ProductRepository interface {
FindByID(ctx context.Context, id uuid.UUID) (*Product, error)
FindBySKU(ctx context.Context, sku SKU) (*Product, error)
List(ctx context.Context, filter ProductFilter) ([]*Product, error)
Save(ctx context.Context, product *Product) error
}
type OrderRepository interface {
FindByID(ctx context.Context, id uuid.UUID) (*Order, error)
FindByUserID(ctx context.Context, userID uuid.UUID) ([]*Order, error)
Save(ctx context.Context, order *Order) error
}
🔄 Domain Events
Copy
// Domain Events
type DomainEvent interface {
OccurredAt() time.Time
EventType() string
}
type UserRegisteredEvent struct {
UserID uuid.UUID
Email string
OccurredOn time.Time
}
type OrderCreatedEvent struct {
OrderID uuid.UUID
UserID uuid.UUID
TotalAmount decimal.Decimal
OccurredOn time.Time
}
type PaymentCompletedEvent struct {
OrderID uuid.UUID
Amount decimal.Decimal
Method string
OccurredOn time.Time
}