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.