阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 服务层与业务逻辑封装

服务层与业务逻辑封装

作者:陈川 阅读数:42372人阅读 分类: Node.js

服务层与业务逻辑封装

Koa2作为一款轻量级的Node.js框架,其核心设计理念是通过中间件机制处理HTTP请求。在实际项目开发中,良好的分层架构能够显著提升代码的可维护性和可扩展性。服务层作为业务逻辑的核心承载层,负责处理具体的业务规则和数据操作,而控制器则专注于HTTP请求的接收与响应。

为什么需要服务层

直接在前端控制器中编写业务逻辑会导致代码臃肿且难以测试。当多个路由需要相同业务逻辑时,代码重复问题会变得严重。服务层的引入将业务逻辑从HTTP上下文中解耦出来,使得业务规则可以独立于Web框架存在。

// 不好的实践:业务逻辑直接写在控制器中
router.get('/users/:id', async (ctx) => {
  const user = await User.findOne({ where: { id: ctx.params.id } })
  if (!user) {
    ctx.status = 404
    return
  }
  // 复杂的业务逻辑...
  ctx.body = user
})

// 好的实践:业务逻辑封装在服务层
class UserService {
  async getUser(id) {
    const user = await User.findOne({ where: { id } })
    if (!user) throw new Error('User not found')
    // 复杂的业务逻辑...
    return user
  }
}

服务层的职责边界

服务层应当专注于业务规则的实现,而不是基础设施的细节。典型的服务层职责包括:

  • 业务规则的执行与验证
  • 数据聚合与转换
  • 事务管理
  • 领域事件触发
  • 与其他服务的协调
class OrderService {
  constructor({ productService, inventoryService }) {
    this.productService = productService
    this.inventoryService = inventoryService
  }

  async createOrder(userId, productId, quantity) {
    // 验证产品是否存在
    const product = await this.productService.getProduct(productId)
    
    // 检查库存
    await this.inventoryService.checkStock(productId, quantity)
    
    // 创建订单
    const order = await Order.create({
      userId,
      productId,
      quantity,
      totalPrice: product.price * quantity
    })
    
    // 扣减库存
    await this.inventoryService.reduceStock(productId, quantity)
    
    // 发送订单创建事件
    eventEmitter.emit('order.created', order)
    
    return order
  }
}

服务层的组织方式

项目规模不同,服务层的组织方式也有所差异。小型项目可以按功能模块划分服务,大型项目则可能需要更细粒度的分层。

按功能模块划分

services/
  user.service.js
  product.service.js
  order.service.js
  auth.service.js

按领域驱动设计划分

src/
  domain/
    user/
      user.service.js
      user.repository.js
    product/
      product.service.js
      product.repository.js
    order/
      order.service.js
      order.repository.js

服务层的依赖注入

服务层通常会依赖其他服务或基础设施组件。通过依赖注入可以更好地管理这些依赖关系,使服务更易于测试和维护。

// 依赖注入容器配置
const container = {
  userService: new UserService({
    userRepository: new UserRepository(),
    emailService: new EmailService()
  }),
  orderService: new OrderService({
    orderRepository: new OrderRepository(),
    userService: container.userService,
    paymentService: new PaymentService()
  })
}

// 在Koa中间件中使用
app.use(async (ctx, next) => {
  ctx.services = {
    user: container.userService,
    order: container.orderService
  }
  await next()
})

// 控制器中使用服务
router.post('/orders', async (ctx) => {
  const order = await ctx.services.order.createOrder(
    ctx.state.user.id,
    ctx.request.body.productId,
    ctx.request.body.quantity
  )
  ctx.body = order
})

服务层的错误处理

服务层应当抛出业务相关的异常,而不是HTTP状态码。控制器负责捕获这些异常并转换为适当的HTTP响应。

class UserService {
  async updateProfile(userId, profileData) {
    const user = await this.userRepository.findById(userId)
    if (!user) throw new BusinessError('USER_NOT_FOUND')
    
    if (profileData.email) {
      const existing = await this.userRepository.findByEmail(profileData.email)
      if (existing && existing.id !== userId) {
        throw new BusinessError('EMAIL_ALREADY_EXISTS')
      }
    }
    
    return this.userRepository.update(userId, profileData)
  }
}

// 控制器中处理错误
router.put('/profile', async (ctx) => {
  try {
    const updated = await ctx.services.user.updateProfile(
      ctx.state.user.id,
      ctx.request.body
    )
    ctx.body = updated
  } catch (err) {
    if (err instanceof BusinessError) {
      ctx.status = 400
      ctx.body = { error: err.message }
    } else {
      throw err
    }
  }
})

服务层的单元测试

由于服务层不依赖HTTP上下文,因此更容易进行单元测试。可以使用各种测试框架来验证业务逻辑的正确性。

describe('OrderService', () => {
  let orderService
  let mockProductService
  let mockInventoryService

  beforeEach(() => {
    mockProductService = {
      getProduct: jest.fn().mockResolvedValue({ id: 1, price: 100 })
    }
    mockInventoryService = {
      checkStock: jest.fn().mockResolvedValue(true),
      reduceStock: jest.fn().mockResolvedValue(true)
    }
    orderService = new OrderService({
      productService: mockProductService,
      inventoryService: mockInventoryService
    })
  })

  it('should create order and reduce stock', async () => {
    const order = await orderService.createOrder(1, 1, 2)
    expect(order.totalPrice).toBe(200)
    expect(mockInventoryService.reduceStock).toHaveBeenCalledWith(1, 2)
  })

  it('should throw when product not found', async () => {
    mockProductService.getProduct.mockResolvedValue(null)
    await expect(orderService.createOrder(1, 999, 1))
      .rejects.toThrow('Product not found')
  })
})

服务层与事务管理

复杂的业务操作通常需要跨多个数据修改操作的事务支持。服务层是管理事务的理想位置。

class TransactionalOrderService {
  constructor({ orderRepository, sequelize }) {
    this.orderRepository = orderRepository
    this.sequelize = sequelize
  }

  async createOrderWithTransaction(userId, items) {
    return this.sequelize.transaction(async (t) => {
      const order = await this.orderRepository.create(
        { userId, status: 'pending' },
        { transaction: t }
      )
      
      for (const item of items) {
        await this.orderRepository.addItem(
          order.id,
          item.productId,
          item.quantity,
          { transaction: t }
        )
      }
      
      return this.orderRepository.findById(order.id, { transaction: t })
    })
  }
}

服务层的性能优化

服务层中可以实施各种性能优化策略,如批量操作、缓存和异步处理等。

class ProductService {
  constructor({ productRepository, cache }) {
    this.productRepository = productRepository
    this.cache = cache
  }

  async getProduct(id) {
    const cacheKey = `product:${id}`
    let product = await this.cache.get(cacheKey)
    
    if (!product) {
      product = await this.productRepository.findById(id)
      if (product) {
        await this.cache.set(cacheKey, product, 3600) // 缓存1小时
      }
    }
    
    return product
  }

  async batchGetProducts(ids) {
    // 先尝试从缓存获取
    const cacheKeys = ids.map(id => `product:${id}`)
    const cachedProducts = await this.cache.mget(cacheKeys)
    
    // 找出未缓存的ID
    const missingIds = ids.filter((id, index) => !cachedProducts[index])
    let missingProducts = []
    
    if (missingIds.length > 0) {
      missingProducts = await this.productRepository.findByIds(missingIds)
      
      // 将新获取的产品存入缓存
      const cacheSets = missingProducts.map(product => [
        `product:${product.id}`,
        product,
        3600
      ])
      await this.cache.mset(cacheSets)
    }
    
    // 合并缓存和数据库结果
    return ids.map(id => 
      cachedProducts.find(p => p?.id === id) || 
      missingProducts.find(p => p.id === id)
    )
  }
}

服务层的版本管理

当业务逻辑需要变更时,可以通过版本控制来平滑过渡,避免破坏现有功能。

class UserServiceV1 {
  async getUser(id) {
    // 旧版实现
  }
}

class UserServiceV2 {
  async getUser(id) {
    // 新版实现
  }
}

// 根据请求头或参数决定使用哪个版本
router.get('/users/:id', async (ctx) => {
  const version = ctx.get('X-API-Version') || 'v1'
  const service = version === 'v2' 
    ? new UserServiceV2() 
    : new UserServiceV1()
  
  ctx.body = await service.getUser(ctx.params.id)
})

服务层的监控与日志

在服务层添加适当的监控和日志可以帮助追踪业务操作的执行情况。

class MonitoredOrderService {
  constructor({ orderService, metrics, logger }) {
    this.orderService = orderService
    this.metrics = metrics
    this.logger = logger
  }

  async createOrder(userId, items) {
    const start = Date.now()
    this.logger.info(`Creating order for user ${userId}`)
    
    try {
      const order = await this.orderService.createOrder(userId, items)
      const duration = Date.now() - start
      
      this.metrics.timing('order.create.success', duration)
      this.logger.info(`Order ${order.id} created successfully`)
      
      return order
    } catch (error) {
      this.metrics.increment('order.create.failure')
      this.logger.error(`Order creation failed: ${error.message}`)
      throw error
    }
  }
}

服务层的扩展模式

随着业务复杂度的增加,服务层可以采用多种扩展模式来保持代码的组织性。

装饰器模式增强服务

function withLogging(service) {
  return {
    async createOrder(...args) {
      console.log('Creating order with args:', args)
      const result = await service.createOrder(...args)
      console.log('Order created:', result.id)
      return result
    },
    // 代理其他方法
    async getOrder(id) {
      return service.getOrder(id)
    }
  }
}

const basicOrderService = new OrderService({ /* deps */ })
const loggedOrderService = withLogging(basicOrderService)

策略模式处理不同业务场景

class DiscountStrategy {
  calculate(order) {
    throw new Error('Not implemented')
  }
}

class NoDiscount extends DiscountStrategy {
  calculate(order) {
    return 0
  }
}

class PercentageDiscount extends DiscountStrategy {
  constructor(percentage) {
    super()
    this.percentage = percentage
  }

  calculate(order) {
    return order.subtotal * this.percentage / 100
  }
}

class OrderPricingService {
  constructor(discountStrategy = new NoDiscount()) {
    this.discountStrategy = discountStrategy
  }

  setStrategy(strategy) {
    this.discountStrategy = strategy
  }

  calculateTotal(order) {
    const discount = this.discountStrategy.calculate(order)
    return order.subtotal - discount
  }
}

// 使用示例
const pricingService = new OrderPricingService()
pricingService.setStrategy(new PercentageDiscount(10)) // 10%折扣
const total = pricingService.calculateTotal({ subtotal: 100 })

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益,请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌