Service Architecture
Comprehensive overview of service organization, core services, service communication patterns, and service responsibilities.
Service Architecture
The service architecture provides a modular, maintainable structure for organizing business logic and system functionality.
Service Organization
Service Layer Structure
┌─────────────────────────────────────────────────────────────┐
│ Service Layer │
├─────────────────────────────────────────────────────────────┤
│ Core Services │ Supporting Services │
│ ├── ProductService │ ├── NotificationService │
│ ├── InventoryService │ ├── AuditService │
│ ├── OrderService │ ├── ReportingService │
│ ├── CustomerService │ ├── EmailService │
│ └── UserService │ └── FileService │
├─────────────────────────────────────────────────────────────┤
│ Infrastructure Services │
│ ├── DatabaseService │ ├── CacheService │
│ ├── AuthService │ ├── QueueService │
│ └── ConfigService │ └── LoggingService │
└─────────────────────────────────────────────────────────────┘
Service Categories
Core Business Services
- Domain Logic: Primary business functionality
- Entity Management: CRUD operations for business entities
- Business Rules: Complex business rule implementation
- Workflow Orchestration: Multi-step business processes
Supporting Services
- Cross-cutting Concerns: Logging, caching, notifications
- Utility Functions: Common helper functionality
- Integration Services: External system communication
- Reporting Services: Data analysis and reporting
Infrastructure Services
- Data Access: Database abstraction and management
- Authentication: User identity and session management
- Configuration: System settings and environment management
- Monitoring: Health checks and performance tracking
Core Services
ProductService
Manages product catalog and product-related operations.
export interface IProductService {
// Product CRUD operations
getProduct(id: string): Promise<Product>
getProducts(filters: ProductFilters): Promise<Product[]>
createProduct(data: CreateProductData): Promise<Product>
updateProduct(id: string, data: UpdateProductData): Promise<Product>
deleteProduct(id: string): Promise<void>
// Product search and filtering
searchProducts(query: string, filters?: ProductFilters): Promise<Product[]>
getProductsByCategory(categoryId: string): Promise<Product[]>
getProductsBySupplier(supplierId: string): Promise<Product[]>
// Product variants
getProductVariants(productId: string): Promise<ProductVariant[]>
createProductVariant(productId: string, data: CreateVariantData): Promise<ProductVariant>
// Product analytics
getProductPerformance(productId: string, dateRange: DateRange): Promise<ProductPerformance>
getTopSellingProducts(limit: number, dateRange: DateRange): Promise<Product[]>
}
export class ProductService implements IProductService {
constructor(
private db: DatabaseService,
private cache: CacheService,
private audit: AuditService
) {}
async getProduct(id: string): Promise<Product> {
// Check cache first
const cached = await this.cache.get(`product:${id}`)
if (cached) {
return cached as Product
}
// Fetch from database
const { data, error } = await this.db.client
.from('products')
.select(`
*,
category:categories(*),
supplier:suppliers(*),
variants:product_variants(*)
`)
.eq('id', id)
.single()
if (error) {
throw new ServiceError('Failed to fetch product', error)
}
if (!data) {
throw new NotFoundError('Product not found')
}
// Cache result
await this.cache.set(`product:${id}`, data, { ttl: 300 })
return data
}
async createProduct(data: CreateProductData): Promise<Product> {
// Validate business rules
await this.validateProductData(data)
// Check for duplicate SKU
const existingProduct = await this.findProductBySku(data.sku)
if (existingProduct) {
throw new BusinessError('Product SKU already exists')
}
// Create product
const { data: product, error } = await this.db.client
.from('products')
.insert(data)
.select()
.single()
if (error) {
throw new ServiceError('Failed to create product', error)
}
// Clear related caches
await this.cache.invalidatePattern('products:*')
// Audit log
await this.audit.log('product.created', {
productId: product.id,
productName: product.name,
data
})
return product
}
private async validateProductData(data: CreateProductData): Promise<void> {
if (data.price <= 0) {
throw new ValidationError('Product price must be greater than zero')
}
if (!data.categoryId) {
throw new ValidationError('Product category is required')
}
// Validate category exists
const category = await this.db.client
.from('categories')
.select('id')
.eq('id', data.categoryId)
.single()
if (!category.data) {
throw new ValidationError('Invalid category ID')
}
}
}
InventoryService
Manages inventory levels, movements, and stock tracking.
export interface IInventoryService {
// Stock level management
getStockLevel(productId: string, warehouseId?: string): Promise<StockLevel>
getStockLevels(filters: StockFilters): Promise<StockLevel[]>
getLowStockItems(threshold?: number): Promise<StockLevel[]>
// Stock movements
recordMovement(movement: StockMovement): Promise<void>
getMovementHistory(productId: string, dateRange?: DateRange): Promise<StockMovement[]>
// Inventory adjustments
adjustInventory(adjustment: InventoryAdjustment): Promise<void>
getAdjustmentHistory(filters: AdjustmentFilters): Promise<InventoryAdjustment[]>
// Reservations
reserveStock(productId: string, quantity: number, orderId: string): Promise<void>
releaseReservation(productId: string, orderId: string): Promise<void>
// Transfers
transferStock(transfer: StockTransfer): Promise<void>
getTransferHistory(filters: TransferFilters): Promise<StockTransfer[]>
}
export class InventoryService implements IInventoryService {
constructor(
private db: DatabaseService,
private productService: ProductService,
private notificationService: NotificationService,
private audit: AuditService
) {}
async recordMovement(movement: StockMovement): Promise<void> {
// Validate movement data
await this.validateMovement(movement)
// Start database transaction
const { error } = await this.db.client.rpc('record_stock_movement', {
p_product_id: movement.productId,
p_warehouse_id: movement.warehouseId,
p_movement_type: movement.type,
p_quantity: movement.quantity,
p_reference: movement.reference,
p_notes: movement.notes
})
if (error) {
throw new ServiceError('Failed to record stock movement', error)
}
// Check for low stock alerts
await this.checkLowStockAlerts(movement.productId)
// Audit log
await this.audit.log('inventory.movement', {
productId: movement.productId,
type: movement.type,
quantity: movement.quantity,
warehouseId: movement.warehouseId
})
}
async reserveStock(productId: string, quantity: number, orderId: string): Promise<void> {
// Check available stock
const stockLevel = await this.getStockLevel(productId)
const availableStock = stockLevel.onHand - stockLevel.reserved
if (availableStock < quantity) {
throw new InsufficientStockError(
`Insufficient stock. Available: ${availableStock}, Requested: ${quantity}`
)
}
// Create reservation
const { error } = await this.db.client
.from('stock_reservations')
.insert({
product_id: productId,
quantity,
order_id: orderId,
expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours
})
if (error) {
throw new ServiceError('Failed to reserve stock', error)
}
// Audit log
await this.audit.log('inventory.reserved', {
productId,
quantity,
orderId
})
}
private async checkLowStockAlerts(productId: string): Promise<void> {
const product = await this.productService.getProduct(productId)
const stockLevel = await this.getStockLevel(productId)
if (stockLevel.onHand <= product.reorderPoint) {
await this.notificationService.sendLowStockAlert({
productId,
productName: product.name,
currentStock: stockLevel.onHand,
reorderPoint: product.reorderPoint
})
}
}
}
OrderService
Manages sales and purchase orders, order processing workflows.
export interface IOrderService {
// Order management
getOrder(id: string): Promise<Order>
getOrders(filters: OrderFilters): Promise<Order[]>
createOrder(data: CreateOrderData): Promise<Order>
updateOrderStatus(id: string, status: OrderStatus): Promise<Order>
cancelOrder(id: string, reason: string): Promise<void>
// Order processing
processOrder(id: string): Promise<void>
fulfillOrder(id: string, fulfillmentData: FulfillmentData): Promise<void>
// Order calculations
calculateOrderTotal(items: OrderItem[]): Promise<OrderTotal>
applyDiscount(orderId: string, discount: Discount): Promise<Order>
// Order analytics
getOrderMetrics(dateRange: DateRange): Promise<OrderMetrics>
getCustomerOrderHistory(customerId: string): Promise<Order[]>
}
export class OrderService implements IOrderService {
constructor(
private db: DatabaseService,
private inventoryService: InventoryService,
private customerService: CustomerService,
private productService: ProductService,
private notificationService: NotificationService,
private audit: AuditService
) {}
async createOrder(data: CreateOrderData): Promise<Order> {
// Validate customer
const customer = await this.customerService.getCustomer(data.customerId)
if (!customer) {
throw new ValidationError('Invalid customer ID')
}
// Validate and process order items
const processedItems = await this.processOrderItems(data.items)
// Calculate totals
const totals = await this.calculateOrderTotal(processedItems)
// Create order in transaction
const { data: order, error } = await this.db.client.rpc('create_order', {
p_customer_id: data.customerId,
p_items: processedItems,
p_subtotal: totals.subtotal,
p_tax: totals.tax,
p_shipping: totals.shipping,
p_total: totals.total,
p_notes: data.notes
})
if (error) {
throw new ServiceError('Failed to create order', error)
}
// Reserve inventory for order items
await this.reserveOrderInventory(order.id, processedItems)
// Send order confirmation
await this.notificationService.sendOrderConfirmation(order)
// Audit log
await this.audit.log('order.created', {
orderId: order.id,
customerId: data.customerId,
total: totals.total
})
return order
}
async processOrder(orderId: string): Promise<void> {
const order = await this.getOrder(orderId)
if (order.status !== 'pending') {
throw new BusinessError('Order cannot be processed in current status')
}
try {
// Update order status
await this.updateOrderStatus(orderId, 'processing')
// Create pick list
await this.createPickList(order)
// Notify warehouse
await this.notificationService.notifyWarehouse('order_ready_for_picking', {
orderId: order.id,
items: order.items
})
// Audit log
await this.audit.log('order.processing_started', { orderId })
} catch (error) {
// Rollback status change
await this.updateOrderStatus(orderId, 'pending')
throw error
}
}
private async processOrderItems(items: CreateOrderItem[]): Promise<OrderItem[]> {
return Promise.all(
items.map(async (item) => {
// Validate product exists and is active
const product = await this.productService.getProduct(item.productId)
if (!product.active) {
throw new ValidationError(`Product ${product.name} is not active`)
}
// Check inventory availability
const stockLevel = await this.inventoryService.getStockLevel(item.productId)
const availableStock = stockLevel.onHand - stockLevel.reserved
if (availableStock < item.quantity) {
throw new InsufficientStockError(
`Insufficient stock for ${product.name}. Available: ${availableStock}`
)
}
return {
productId: item.productId,
productName: product.name,
quantity: item.quantity,
unitPrice: product.price,
totalPrice: product.price * item.quantity
}
})
)
}
private async reserveOrderInventory(orderId: string, items: OrderItem[]): Promise<void> {
for (const item of items) {
await this.inventoryService.reserveStock(
item.productId,
item.quantity,
orderId
)
}
}
}
Service Communication Patterns
Direct Service Calls
// Synchronous service communication
export class OrderFulfillmentService {
constructor(
private orderService: OrderService,
private inventoryService: InventoryService,
private shippingService: ShippingService,
private customerService: CustomerService
) {}
async fulfillOrder(orderId: string): Promise<void> {
// Get order details
const order = await this.orderService.getOrder(orderId)
// Pick items from inventory
const pickingResults = await this.inventoryService.pickItems(
order.items.map(item => ({
productId: item.productId,
quantity: item.quantity
}))
)
// Create shipping label
const customer = await this.customerService.getCustomer(order.customerId)
const shippingLabel = await this.shippingService.createShippingLabel({
to: customer.shippingAddress,
items: order.items,
orderId: order.id
})
// Update order status
await this.orderService.updateOrderStatus(orderId, 'shipped')
// Send tracking information
await this.customerService.sendTrackingInfo(
order.customerId,
shippingLabel.trackingNumber
)
}
}
Event-Driven Communication
// Event-driven service communication
export class ServiceEventBus {
private handlers: Map<string, Array<(data: any) => Promise<void>>> = new Map()
async emit(event: string, data: any): Promise<void> {
const eventHandlers = this.handlers.get(event) || []
// Execute handlers in parallel with error handling
const results = await Promise.allSettled(
eventHandlers.map(handler => handler(data))
)
// Log any failed handlers
results.forEach((result, index) => {
if (result.status === 'rejected') {
logger.error('Event handler failed', {
event,
handlerIndex: index,
error: result.reason
})
}
})
}
on(event: string, handler: (data: any) => Promise<void>): void {
const handlers = this.handlers.get(event) || []
this.handlers.set(event, [...handlers, handler])
}
}
// Service event handlers
export class InventoryEventHandlers {
constructor(
private inventoryService: InventoryService,
private notificationService: NotificationService,
private reportingService: ReportingService
) {
this.setupEventHandlers()
}
private setupEventHandlers(): void {
eventBus.on('order.created', this.handleOrderCreated.bind(this))
eventBus.on('order.cancelled', this.handleOrderCancelled.bind(this))
eventBus.on('product.discontinued', this.handleProductDiscontinued.bind(this))
}
private async handleOrderCreated(order: Order): Promise<void> {
// Reserve inventory for order items
for (const item of order.items) {
await this.inventoryService.reserveStock(
item.productId,
item.quantity,
order.id
)
}
// Update inventory reports
await this.reportingService.updateInventoryMetrics()
}
private async handleOrderCancelled(order: Order): Promise<void> {
// Release reserved inventory
for (const item of order.items) {
await this.inventoryService.releaseReservation(
item.productId,
order.id
)
}
}
}
Async Service Communication
// Queue-based async communication
export class AsyncServiceProcessor {
constructor(
private queueService: QueueService,
private services: Map<string, any>
) {
this.startProcessing()
}
async enqueueOperation(serviceName: string, operation: string, data: any): Promise<void> {
const message = {
id: generateId(),
serviceName,
operation,
data,
timestamp: new Date().toISOString(),
retryCount: 0
}
await this.queueService.enqueue('service-operations', message)
}
private async startProcessing(): void {
while (true) {
try {
const message = await this.queueService.dequeue('service-operations', 5000)
if (message) {
await this.processMessage(message)
}
} catch (error) {
logger.error('Error processing service queue', { error })
await new Promise(resolve => setTimeout(resolve, 1000))
}
}
}
private async processMessage(message: any): Promise<void> {
try {
const service = this.services.get(message.serviceName)
if (!service) {
throw new Error(`Service ${message.serviceName} not found`)
}
const method = service[message.operation]
if (!method) {
throw new Error(`Operation ${message.operation} not found on service ${message.serviceName}`)
}
await method.call(service, message.data)
logger.info('Service operation completed', {
id: message.id,
serviceName: message.serviceName,
operation: message.operation
})
} catch (error) {
logger.error('Service operation failed', {
id: message.id,
serviceName: message.serviceName,
operation: message.operation,
error: error.message,
retryCount: message.retryCount
})
// Retry logic
if (message.retryCount < 3) {
message.retryCount++
message.nextRetry = new Date(Date.now() + Math.pow(2, message.retryCount) * 1000)
await this.queueService.enqueue('service-operations', message, message.nextRetry)
} else {
// Send to dead letter queue
await this.queueService.enqueue('failed-operations', message)
}
}
}
}
Service Monitoring and Health
Service Health Checks
// Service health monitoring
export interface ServiceHealth {
name: string
status: 'healthy' | 'degraded' | 'unhealthy'
timestamp: string
metrics: {
responseTime: number
errorRate: number
throughput: number
}
dependencies: {
[key: string]: 'healthy' | 'unhealthy'
}
}
export class ServiceHealthChecker {
constructor(private services: Map<string, any>) {}
async checkHealth(serviceName: string): Promise<ServiceHealth> {
const service = this.services.get(serviceName)
if (!service) {
throw new Error(`Service ${serviceName} not found`)
}
const startTime = Date.now()
let status: ServiceHealth['status'] = 'healthy'
let dependencies: ServiceHealth['dependencies'] = {}
try {
// Check service health endpoint if available
if (service.healthCheck) {
await service.healthCheck()
}
// Check dependencies
if (service.dependencies) {
for (const dep of service.dependencies) {
try {
await this.checkDependency(dep)
dependencies[dep] = 'healthy'
} catch (error) {
dependencies[dep] = 'unhealthy'
status = 'degraded'
}
}
}
} catch (error) {
status = 'unhealthy'
}
const responseTime = Date.now() - startTime
return {
name: serviceName,
status,
timestamp: new Date().toISOString(),
metrics: {
responseTime,
errorRate: await this.getErrorRate(serviceName),
throughput: await this.getThroughput(serviceName)
},
dependencies
}
}
async getAllHealthStatuses(): Promise<ServiceHealth[]> {
const services = Array.from(this.services.keys())
return Promise.all(services.map(name => this.checkHealth(name)))
}
}
This service architecture provides a solid foundation for building scalable, maintainable applications with clear separation of concerns and robust communication patterns.