Reconfiguration experience of KOA middleware system

Zhilian front end 2021-02-23 03:47:54
reconfiguration experience koa middleware


The big front end of Zhilian recruitment Ada Provided Web The server can run on both the server side and the local development environment , Its core is Web frame Koa.Koa It is famous for its good support for asynchronous programming , What's equally praiseworthy is its middleware mechanism . Essentially ,Koa It's actually a middleware runtime , Almost all the actual functions are registered and implemented in the form of middleware .

present situation

Ada from 1.0.0 Version began to introduce independent @zpfe/koa-middleware modular , For maintenance Web Middleware required in services . This module exports all the middleware separately ,Web Services can be registered on demand (use). With the continuous improvement of functions , There are more than ten Middleware in this module .@zpfe/koa-middleware The usage of the module is as follows :

const app = new Koa()
app.use(middleware1)
app.use(middleware2)
// ...
app.use(middlewareN)
 Copy code 

Between middleware, implicit Appointment The order of execution , But the order of execution is control Given to two users ( Rendering services and API service ), This means that the user must know the technical details of each middleware , This is a “ Bad taste ” One of .

The figure below shows the coupling between the user and middleware :

Koa Middleware architecture is an onion structure , Every middleware can be regarded as an onion skin . The first to register is in the outermost layer , The last one registered is in the innermost layer . When executed , It goes from the outermost layer to the innermost layer , And then in reverse order back to the outermost layer . The following figure shows Koa The implementation of middleware :

Every middleware has two executable opportunities , And in our scenario , Most middleware actually has only one piece of logic . With the expansion of middleware , The complete execution trajectory becomes too complex , Increases the cost of debugging and understanding , This is a “ Bad taste ” The second .

For the above reasons , We decided to be right @zpfe/koa-middleware Module refactoring , Further improve its ease of use 、 Cohesion and maintainability .

analysis

First, analyze one by one @zpfe/koa-middleware The exported function and usage , You'll find the following patterns :

  • The registration order of middleware is consistent between the two users ;
  • There are some middleware only in API Registered in the service ( such as CORS and CSRF);
  • There are some middleware whose parameters or implementations are different between the two users ( Like parsers and portal processors );
  • There are some features that are not really middleware ( Such as request context and fuse ).

This means that we can take back the registration right of middleware , It also allows users to control the opening and closing state of individual middleware through parameters 、 Parameters 、 Even realize . It can also extract non middleware functions directly into new modules .

Next, look at the execution order of these middleware , They can be classified into several different types :

  • The initializer : Responsible for initializing data or functions ( For example, initialization x-zp-request-id And log function );
  • Blocker : Responsible for interrupting execution ( such as CORS and CSRF);
  • The preprocessor : Responsible for preparing the environment needed to process the request ( Like parsers );
  • processor : Responsible for handling requests ( Like diagnostics and portal processors );
  • Post processor : Responsible for the sorting work after the request processing is completed ( Like cleaning up temporary files and data ).

Further analysis of the middleware included in each category , It will be found that their implementation is highly consistent within the classification . Except that the preprocessor and processor need to execute asynchronously , The other types of middleware can all be executed in a synchronous way .

Mentioned above Koa Middleware will be executed twice ,@zpfe/koa-middleware It does contain some middleware like this ( For example, log function ). Just now when I was classifying middleware , This middleware is split into two parts , Into different categories . such as , The logging function will be split into initializers ( Initialize the log function ) And postprocessor ( Record the end of the request ). For functions like this , We can change our thinking , Think of it as a complete set of functions , But it exports two different types of specific functions . such , We can write all the code of logging function in the same file , The initialization function and post-processing function are defined as different functions to export .

principle

Through the analysis of , We're right @zpfe/koa-middleware There is a clear understanding of the status quo of the module . Now to summarize , Form some useful guidelines :

  • Principle of single responsibility (SRP): Pull away from non middleware functions ;
  • The principle of Dependence Inversion (DIP): Do not expose the functional details of middleware to users ;
  • Self cleaning : After processing the request , Middleware has to clean up the data it generates ;
  • Easy to test : Each component can be tested individually ;
  • Incremental refactoring : Refactoring in stages , Every stage doesn't destroy existing functions , Have the ability to publish separately .

Stage

First step : Pull away from non middleware functions

This step is relatively simple , You just need to extract these non middleware function files into independent modules . It should be noted that :

  • Independent modules should meet the standards of high cohesion and low coupling ;
  • Unit tests should also be extracted into separate modules , And appropriate modification to meet the test standard ;
  • All users switch to independent modules one by one , And modify the unit test appropriately ;
  • Control the scope of refactoring , Limit changes to non middleware and its users .

After removing the non middleware functions ,@zpfe/koa-middleware Module is now a real middleware module .

The following figure shows the code structure after pulling out the non middleware function :

The second step : Encapsulate the registration function

Next, encapsulate a registration function , And as the only external export , To simplify the user's code , And hide the middleware details .

Based on the previous analysis , This registration function needs parameters to allow users to configure some middleware . The main logic of the new registration function is shown below :

function registerTo(koaApp, options) {
koaApp.use(middleware1)
koaApp.use(middleware2)
if (options.config3) koaApp.use(middleware3)
if (options.config4) koaApp.use(middleware4(options.config4))
// ...
koaApp.use(middlewareN)
}
module.exports = {
registerTo
}
 Copy code 

options Parameters can not only be used to control the enabled state of specific middleware , You can also provide configuration to middleware . Users can use the new registration function in this way :

const middleware = require('@zpfe/koa-middleware')
const app = new Koa()
middleware.registerTo(app, {
config3: true,
config4: function () { /* ... */ }
})
 Copy code 

Now the registration order of middleware has been encapsulated in @zpfe/koa-middleware Inside the module , Users only need to know how to use the registration function , Suppose you want to add a middleware in the future , And it won't have a big impact on users .

The following figure shows the code structure after encapsulating the registration function :

It's worth noting that the changes in this step only involve @zpfe/koa-middleware The main file and user of the module , No changes have been made to any middleware , It follows the principle of gradual reconstruction . After supplementing and updating unit tests , You can go on to the next step .

The third step : Refactoring initializers

Based on the previous analysis , There are several types of middleware , Initializer is the first of these . The middleware contained in the initializer should be registered and managed by itself , The main logic of the initializer is shown below :

function register(koaApp, options) {
koaApp.use(middleware1)
// ...
koaApp.use(middlewareN)
}
module.exports = register
 Copy code 

It looks like @zpfe/koa-middleware Copy of module master file , I'm going to modify @zpfe/koa-middleware Module master file , Replace the code that registers the initializer middleware one by one with using the initializer to register uniformly :

const initiators = require('./initiators')
function registerTo(koaApp, options) {
initiators(koaApp, { configN: options.configN })
if (options.config3) koaApp.use(middleware3)
if (options.config4) koaApp.use(middleware4(options.config4))
// ...
koaApp.use(middlewareN)
}
 Copy code 

now ,@zpfe/koa-middleware The main file of the module only interacts with the initializer , No longer interact with multiple middleware contained in the latter . in other words , We have hidden the logic details of initializer middleware . Next, to further refactor the logic , So it won't go beyond the scope of the initializer .

The middleware included in the initializer is executed synchronously , They can be reduced to functions , Organize into a function queue , Execute in order . The following shows the modified initializer :

const task1 = require('./tasks1')
const taskN = require('./tasksn')
function register(koaApp, options) {
const tasks = []
if (options.config1) tasks.push(task1)
// ...
if (options.configN) tasks.push(taskN)
async function initiate (ctx, next) {
tasks.forEach(task => task(ctx))
return next()
}
koaApp.use(initiate)
}
 Copy code 

All initializer types of middleware are reduced to synchronization functions , And create a task list according to the parameters passed in during registration , Then register yourself as a middleware that performs the task list in order .

After supplementing and updating unit tests , The reconfiguration of the initializer is declared complete . In this step , We combine multiple middleware into one , And encapsulate its logic inside , It will make @zpfe/koa-middleware Module code is more structured , It's also easier to maintain .

The following figure shows the code structure after refactoring the initializer :

Review all the refactoring operations in this step , We will find that there is no user involved , This is the benefit of hiding internal logic in the second step of refactoring . similarly , We haven't made any changes to the middleware of the uninitializer , These middleware are not in the scope of this step , We'll refactor in the next steps .

Step four : Refactoring the rest of the middleware types in order

After the initializer refactoring is complete , We can reconstruct the other middleware types in the same way : Blocker 、 The preprocessor 、 Processors and postprocessors .

The code structure after the refactoring is shown in the figure below :

It's still important to control the scope of refactoring , Complete a type of refactoring ( Include unit tests ) after , Let's move on to the next type .

Step five : Overall inspection

Now the refactoring is coming to an end . For users ,@zpfe/koa-middleware The module only exposes one function , Greatly improved ease of use ; Yes @zpfe/koa-middleware The module itself , Its internal structure is more reasonable 、 The order of execution is easier to predict 、 It's also easier to unit test .

Before declaring refactoring complete , We need to be right about @zpfe/koa-middleware A general inspection of the module , Look for the missing “ Bad taste ”, And gradually accumulated in the process of gradual refactoring “ Bad taste ”.

current @zpfe/koa-middleware The module contains five middleware , Each middleware registration function can control its internal functions through parameters .@zpfe/koa-middleware The main file of the module is responsible for sorting the parameters passed in by the user into the expected parameter format of each middleware , As shown below :

function registerTo(koaApp, options) {
initiators(koaApp, { configN: options.configN })
blockers(koaApp, { configO: options.configO })
preProcessors(koaApp, { configP: options.configP })
processors(koaApp, { configQ: options.configQ })
postProcessors(koaApp, { configR: options.configR })
}
 Copy code 

Since every middleware needs to register functions from options Parameter to get the data you need , Then we can options The structure of parameters is classified according to middleware , After classification, the registration function will look more concise :

function registerTo(koaApp, options) {
initiators(koaApp, options.initiators)
blockers(koaApp, options.blockers)
preProcessors(koaApp, options.preProcessors)
processors(koaApp, options.processors)
postProcessors(koaApp, options.postProcessors)
}
 Copy code 

In the previous analysis , We already know that the initializer generates some data , And hopefully the data can be cleaned up by themselves , This means that there are corresponding tasks in the post processor to clean up the data . Split the initialization and cleanup logic of the same function into two files , It is also a kind of “ Bad taste ”.

The way to deal with this situation is simple , First, find out all the functions with such characteristics , Create separate code files for them . Then move its initialization logic and cleanup logic to the file , And we derive . In this way , Every function will become more cohesive .

The code structure after refactoring is shown in the figure below :

summary

Review the entire refactoring process , It turns out that the first thing we do is not code , It's an in-depth analysis of the current situation . In the process , Seek common ground while reserving differences , Some patterns will come out naturally , They're all refactoring “ material ”.

In real coding , We took a gradual approach , Break the whole process down into steps . Try to make every step complete , The whole module can meet the release standard . This means that the changes involved in each step need to be limited to a controllable scope , And each step needs to include a complete test .

above , It's the difference between refactoring and rewriting .

notes : This article was originally written in 2018 year 8 month 8 It was published in the front-end of the general assembly of Chile Wiki.

版权声明
本文为[Zhilian front end]所创,转载请带上原文链接,感谢
https://qdmana.com/2021/02/20210222134322278g.html

  1. Configure the certificate to enable ngnix to publish the trusted website of HTTPS
  2. Javascript数据类型
  3. HTTP interface debugging tool! 48000 star HTTP command line client!
  4. Parameter encryption of front end URL link band
  5. HTTP interface debugging tool! 48000 star HTTP command line client!
  6. Three front end frameworks: data binding and data flow
  7. Reading Axios source code (1) -- exploring the realization of basic ability
  8. Event bubble and capture in JavaScript
  9. 【微前端】微前端最終章-qiankun指南以及微前端整體探索
  10. R & D solution e-Car front end monitoring system
  11. [JS] 877 - 35 wonderful knowledge of JavaScript, long experience!
  12. R & D solution e-Car front end monitoring system
  13. High performance nginx HTTPS tuning - how to speed up HTTPS by 30%
  14. 解决ajax跨域问题【5种解决方案】
  15. Top ten classic sorting of JavaScript
  16. HTTP 1. X learning notes: an authoritative guide to Web Performance
  17. Vue3 component (IX) Vue + element plus + JSON = dynamic rendering form control component
  18. My http / 1.1 is so slow!
  19. Why Vue uses asynchronous rendering
  20. The response status was 0. Check out the W3C XMLHttpRequest Level 2 spec for
  21. The tapable instance object hook of webpack4. X core tool library
  22. The tapable instance object hook of webpack4. X core tool library
  23. Using libcurl for HTTP communication in C + +
  24. Using libcurl for HTTP communication in C + +
  25. Using CSS variable in Vue
  26. Deeply understand the update of state and props in react
  27. No matter how fast the Internet is, it's useless! In addition to Baidu disk, there is this website slow to let you eat shriveled
  28. Baidu share does not support the solution of HTTPS
  29. [micro front end] the final chapter of micro front end - Qiankun guide and overall exploration of micro front end
  30. [micro front end] the final chapter of micro front end - Qiankun guide and overall exploration of micro front end
  31. Vue cli creates vue3 project
  32. Nginx reverse proxy for windows authentication using NTLM
  33. Rust tutorial: introduction to rust for JavaScript developers
  34. Deploying personal blog to Tencent cloud with serverless framework
  35. R & D solution e-Car front end monitoring system
  36. JavaScript advanced learning
  37. Spend 1 minute to master these 5 ppt tips, courseware making less detours
  38. Vue: vuex persistent state
  39. React native gets the current network state of the device Netinfo
  40. High performance nginx HTTPS tuning - how to speed up HTTPS by 30%
  41. JavaScript advanced: Javascript object-oriented, JavaScript built-in object, JavaScript BOM, JavaScript encapsulation
  42. JavaScript advanced: Javascript object-oriented, JavaScript built-in object, JavaScript BOM, JavaScript encapsulation
  43. Vue determines whether the El form in the elementui is updated or changed. If it changes, it will prompt whether to save it. If it does not change, it will leave directly
  44. Algorithm problem: sum of two numbers -- JavaScript and Java implementation
  45. High performance nginx HTTPS tuning
  46. JQuery advanced
  47. day 30 jQuery
  48. JQuery:JQuery Basic syntax, jQuery selector, jQuery DOM, comprehensive case check box, comprehensive case random picture
  49. TCP/IP 开胃菜 之 HTTP
  50. JQuery:JQuery Basic syntax, jQuery selector, jQuery DOM, comprehensive case check box, comprehensive case random picture
  51. JavaScript data type
  52. [micro front end] the final chapter of micro front end - Qiankun guide and overall exploration of micro front end
  53. Solve Ajax cross domain problem [5 solutions]
  54. HTTP of TCP / IP appetizer
  55. Optimization of pod creation efficiency in serverless scenario
  56. Iqiyi Sports: experience the ultimate expansion and contraction of serverless, and increase the utilization rate of resources by 40%
  57. First knowledge of HTTP / 1.1
  58. First knowledge of HTTP / 1.1
  59. Webpack learning notes series 05 devserver
  60. Webpack learning notes series 04 - resource processing optimization