服务层与业务逻辑封装
服务层与业务逻辑封装
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
上一篇:分层架构设计与模块划分
下一篇:数据访问层的抽象设计