阿里云主机折上折
  • 微信号
Current Site:Index > Code organization and architecture evolution strategy

Code organization and architecture evolution strategy

Author:Chuan Chen 阅读数:6486人阅读 分类: Node.js

Code Organization and Architecture Evolution Strategies

Koa2, as a lightweight web framework in the Node.js ecosystem, provides a flexible foundation for application architecture with its onion-ring model and asynchronous middleware mechanism. However, as business complexity increases, organizing code and planning architecture evolution paths become critical. Below are specific practical solutions from the dimensions of directory structure design, middleware layering, and plugin-based extensibility.

Basic Directory Structure Design

A typical Koa2 project initially adopts a functional dimension division:

/src  
  /controllers  
  /models  
  /routes  
  /middlewares  
  app.js  

When the number of routes exceeds 20, it is recommended to switch to a business module division:

/src  
  /modules  
    /user  
      user.controller.js  
      user.model.js  
      user.router.js  
    /order  
      order.controller.js  
      order.model.js  
      order.router.js  
  /core  
    /middlewares  
    /services  
  app.js  

Automate route registration through dynamic loading:

// app.js  
const fs = require('fs')  
const path = require('path')  

fs.readdirSync('./src/modules').forEach(module => {  
  const router = require(`./modules/${module}/${module}.router`)  
  app.use(router.routes())  
})  

Middleware Layering Strategy

Infrastructure Layer Middleware

Handles basic HTTP capabilities, recommended execution order:

app.use(require('koa-helmet')()) // Security headers  
app.use(require('koa-compress')()) // Compression  
app.use(require('koa-bodyparser')()) // Body parsing  

Business Logic Layer Middleware

Implements specific business rules, typical pattern:

// Order status validation middleware  
async function orderStatusValidator(ctx, next) {  
  const order = await OrderService.getById(ctx.params.id)  
  if (order.status !== 'paid') {  
    ctx.throw(403, 'Order not paid')  
  }  
  ctx.state.order = order // Mount to context  
  await next()  
}  

// Usage in routes  
router.post('/refund',   
  orderStatusValidator,  
  async ctx => {  
    await RefundService.create(ctx.state.order)  
  }  
)  

Plugin-Based Architecture Evolution

When multi-tenancy support is needed, transform via a plugin mechanism:

// plugins/tenant/index.js  
module.exports = function tenantPlugin(app) {  
  app.context.getTenantDB = function() {  
    return this.headers['x-tenant-id']   
      ? getTenantConnection(this.headers['x-tenant-id'])  
      : defaultDB  
  }  
    
  app.use(async (ctx, next) => {  
    ctx.db = ctx.getTenantDB()  
    await next()  
  })  
}  

// app.js  
const tenant = require('./plugins/tenant')  
tenant(app)  

Advanced Configuration Management

Evolution from basic to dynamic configuration:

// config/default.js  
module.exports = {  
  db: {  
    host: process.env.DB_HOST || 'localhost',  
    pool: {  
      max: process.env.NODE_ENV === 'production' ? 20 : 5  
    }  
  }  
}  

// Dynamic configuration loader  
class ConfigLoader {  
  constructor() {  
    this._config = require('./config/default')  
    this.watchFile()  
  }  
    
  watchFile() {  
    fs.watch('./config', (event, filename) => {  
      if (filename.endsWith('.js')) {  
        this._config = require('./config/' + filename)  
      }  
    })  
  }  
    
  get config() {  
    return this._config  
  }  
}  

Exception Handling System Construction

Example of a tiered error handling mechanism:

// core/error.js  
class BusinessError extends Error {  
  constructor(code, message) {  
    super(message)  
    this.code = code  
  }  
}  

// Middleware handling  
app.use(async (ctx, next) => {  
  try {  
    await next()  
  } catch (err) {  
    if (err instanceof BusinessError) {  
      ctx.status = 400  
      ctx.body = { code: err.code, msg: err.message }  
    } else {  
      ctx.status = 500  
      ctx.body = { code: 'SERVER_ERROR', msg: 'System error' }  
      ctx.app.emit('error', err, ctx) // Trigger logging  
    }  
  }  
})  

// Usage in business logic  
router.post('/create', async ctx => {  
  if (!ctx.request.body.name) {  
    throw new BusinessError('INVALID_PARAM', 'Name cannot be empty')  
  }  
})  

Performance Optimization Architecture Adjustments

Architecture transformation for high-concurrency scenarios:

// Introduce route-level caching  
const LRU = require('lru-cache')  
const productCache = new LRU({  
  max: 1000,  
  maxAge: 1000 * 60 * 5 // 5 minutes  
})  

router.get('/products/:id',   
  async (ctx, next) => {  
    const cached = productCache.get(ctx.params.id)  
    if (cached) {  
      ctx.body = cached  
      return  
    }  
    await next()  
  },  
  async ctx => {  
    const product = await ProductService.getDetail(ctx.params.id)  
    productCache.set(ctx.params.id, product)  
    ctx.body = product  
  }  
)  

// Database connection pool optimization  
const knex = require('knex')({  
  client: 'mysql2',  
  connection: {  
    pool: {  
      afterCreate: (conn, done) => {  
        conn.query('SET SESSION wait_timeout = 28800', done)  
      }  
    }  
  }  
})  

Microservices Evolution Path

Gradual transition plan for splitting:

// gateway/proxy.js  
const httpProxy = require('http-proxy-middleware')  

module.exports = function(serviceName) {  
  return async (ctx, next) => {  
    if (ctx.path.startsWith(`/api/${serviceName}`)) {  
      return httpProxy({  
        target: `http://${serviceName}-service:3000`,  
        pathRewrite: { [`^/api/${serviceName}`]: '' },  
        changeOrigin: true  
      })(ctx.req, ctx.res, next)  
    }  
    await next()  
  }  
}  

// app.js  
app.use(require('./gateway/proxy')('user'))  
app.use(require('./gateway/proxy')('order'))  

Introduction of Type System

Hybrid solution for gradually adopting TypeScript:

// ts-check.js  
const { execSync } = require('child_process')  

module.exports = function typesafeMiddleware() {  
  return async (ctx, next) => {  
    try {  
      execSync('npm run type-check', { stdio: 'pipe' })  
      await next()  
    } catch (e) {  
      ctx.status = 500  
      ctx.body = { error: 'Type check failed', details: e.stdout.toString() }  
    }  
  }  
}  

// Enable type checking in development environment  
if (process.env.NODE_ENV === 'development') {  
  app.use(require('./ts-check')())  
}  

Monitoring System Architecture Integration

Example of a production monitoring solution:

// core/monitor.js  
const { NodeSDK } = require('@opentelemetry/sdk-node')  
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')  

module.exports = function initTelemetry() {  
  const sdk = new NodeSDK({  
    traceExporter: new ConsoleSpanExporter(),  
    instrumentations: [getNodeAutoInstrumentations()]  
  })  
    
  sdk.start()  
    
  process.on('SIGTERM', () => {  
    sdk.shutdown()  
      .then(() => console.log('Tracing terminated'))  
      .catch(err => console.error('Error terminating tracing', err))  
  })  
}  

// Import at the top of app.js  
require('./core/monitor')()  

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

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.