当前位置:网站首页>Analyze the implementation principle of the onion model, and connect the onion model in your own project
Analyze the implementation principle of the onion model, and connect the onion model in your own project
2022-06-12 11:43:00 【Jioho_】
Analyze the implementation principle of onion model , Connect the onion model in your own project
Last article First acquaintance with onion model , Analyze the middleware execution process , elementary analysis koa Middleware source A brief introduction to be based on koa The running process of the onion model middleware , I learned how to write middleware
But based on koa The onion model of can only be triggered when a request is made . In our usual projects , How to use the onion model ?
Simple analysis koa Code
koajs/koa The code analysis :
Look for the koa Entrance file
adopt package.json In the document “main” Field found , The entry file is in lib/application.js
see application The execution process in
open application.js see
There are several familiar methods
- listen Method
koa The source code section :
listen (...args) {
debug('listen')
const server = http.createServer(this.callback())
return server.listen(...args)
}
In the actual project , We call listen Usually
app.listen('3000', function() {
console.log(' Listening created successfully ')
})
Plug in koa Source code , You can see that at the beginning http.createServer Created a service
Then we introduced 3000 and Monitor successful The callback functions of are actually passed to server Of
http.createServer The callback function received here deals with the contents when the request is triggered
node-api file :http_createserver_options_requestlistener
Extra knowledge
stay http.createServer The callback function passed in and server.on(‘request’) It's the same effect
So when a request comes in , Execution is this.callback() Method
- callback Method
// compose From the introduction of koa-compose
// const compose = require('koa-compose')
callback () {
const fn = compose(this.middleware)
if (!this.listenerCount('error')) this.on('error', this.onerror)
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res)
return this.handleRequest(ctx, fn)
}
return handleRequest
}
fn It's from
compose(this.middleware)Got , Among themthis.middlewareIs an array of functions , I'll talk about it laterlistenerCountThe method should be Application Inherit Emitter The method in , For the time being, I will not go into further studyWhat is called according to the above code is
this.callback(), Back to youhandleRequestFunction
handleRequest in , Created ctx , This is for koa Customize the context of a response
because
http.createServerIn the callback function of 2 Return values , Namelyreq,res. therefore express Just take this 2 Parameters are returned directly to use , and koa It is an extra layer
- Get ctx after , It's in handleRequest part
- handleRequest Method
handleRequest (ctx, fnMiddleware) {
const res = ctx.res
res.statusCode = 404
const onerror = err => ctx.onerror(err)
const handleResponse = () => respond(ctx)
onFinished(res, onerror)
return fnMiddleware(ctx).then(handleResponse).catch(onerror)
}
- In this part, let's start with statusCode Defined as 404( By default, there is no method to handle ), If there is a response, it will be converted to the corresponding status code
handleResponseIt was introduced from abovefnRespond once after execution , In the end, it will triggerreq.end()Method , Respond to the contentonFinishedIt's from on-finished A library introduced . Used to listen for the end of a response , If the response is wrong , Just execute the corresponding onerror, This should be introduced to listen for errors in requests
- analysis fnMiddleware source
from this.callback We can know fnMiddleware = compose(this.middleware)
among compose yes koa-compose
and this.middleware It is an empty array when initializing , stay use In the method push Go in content , be familiar with koa Of all know , use The method is to mount the middleware
also use The function is very simple , Simple judgment fn Whether a function , And then push go in , Waiting to be passed to compose
use (fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!')
debug('use %s', fn._name || fn.name || '-')
this.middleware.push(fn)
return this
}
application Summary of rough analysis
- listen Method to create a listener
1.1 Usehttp.createServer(this.callback())To create a response to a request - stay callback Function , Called
compose(this.middleware)To get an executable functionfnMiddleware - stay
handleRequestFunction , Added error listening , Mainly callfnMiddlewarefunction , After success, the corresponding value is returned to complete the response this.middlewareFromusefunction , Added middleware , therefore this.middleware It is a list of middleware , So the execution of middleware is in order
koa-compose analysis
original koa The middleware logic of is koajs/compose In this library , and koa Just called this library
According to the old rules , see package.json file , Found that there was no main Entry field of , Back to the root directory, I found that there is only one library index.js Core documents
Onion model all code ~
I have to sigh that the code written by the great God is always so simple but one by one
'use strict'
/** * Expose compositor. */
module.exports = compose
/** * Compose `middleware` returning * a fully valid middleware comprised * of all those which are passed. * * @param {Array} middleware * @return {Function} * @api public */
function compose(middleware) {
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/** * @param {Object} context * @return {Promise} * @api public */
return function(context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch(i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
} catch (err) {
return Promise.reject(err)
}
}
}
}
To put it simply, the results of my analysis :
establish compose, Pass in array , then compose It returns a function ( That's where we are koa See in the source code fnMiddleware), At this time, the execution sequence of this batch of middleware has been determined
When executed
fnWhen , It's like a Recursive function , However, it is implemented through callback function
The code is very clean , Is to define a dispatch Method , thenreturn dispatch(0)
When executed dispatch When ,0Actually, as an index , Get the corresponding methods in the array in orderfn = middleware[i]Let's start with the recursive function
fn(context, dispatch.bind(null, i + 1))
Which translates as fn_0(context,fn_1) When the current function of the middleware is executed , The next function is also mentioned , call next It's called fn_1, If you don't call next That's rightfn_0After execution , The program will continue to go up , That's why you don't call next The next middleware will not execute
As for the beginning koa Support asymc/await The cleverness of grammar lies in the use ofreturn Promise.resolve()Wrap ordinary functions as Promise grammar , So called next You can use .then perhaps await 了One of the most subtle is
if (i === middleware.length) fn = next
Why? next To assign as fn ?
Because when i === middleware.length When , The array of middleware has been completely executed ,next Function is actually middleware[middleware.length] == null
This will triggerif (!fn) return Promise.resolve()It can be understood as the... Of the whole function" Recursive header "
in general , The implementation of the whole onion model is to advance the next method to the current method next Parameters in , It allows you to decide when the next method will be called , And very flexible , For example, the following situations :
Pipelined Mode
A pipeline is a process in which the execution of a method is passed to the next method , For example, the following pseudo code :
app.use(function(ctx, next) {
console.log('1')
next()
})
app.use(function(ctx, next) {
console.log('2')
next()
})
app.use(function(ctx, next) {
console.log('3')
next()
})
next Functions are uniformly called at the end , Then it will print once 1 2 3
Classic onion pattern
app.use(function(ctx, next) {
console.log('1')
next()
console.log('1 - end')
})
app.use(function(ctx, next) {
console.log('2')
next()
console.log('2 - end')
})
app.use(function(ctx, next) {
console.log('3')
next()
console.log('3 - end')
})
stay next There is function processing before and after the function , Layer by layer , It's like an onion
The result is :
1
2
3
3 - end
2 - end
1 - end
Reverse order mode
app.use(function(ctx, next) {
next()
console.log('1')
})
app.use(function(ctx, next) {
next()
console.log('2')
})
app.use(function(ctx, next) {
next()
console.log('3')
})
This is printed in turn 3 2 1 了 .
Connect the onion model in your own project
Through the previous article First acquaintance with onion model , Analyze the middleware execution process , elementary analysis koa Middleware source Add the above koa Source code analysis , Onion model must be more familiar . How to access your own projects ?
To achieve this goal , We have to at least have a set of functions to Collect and store middleware methods ( similar koa Of use), There is one Methods to trigger middleware execution ( similar koa Of handleRequest), Finally, there is a set Middleware mechanism ( Use it directly koa-compose)
- Use koa-compose What are the limitations ?
- That is, when the parameters are fixed, only 2 individual : Namely
contextParameters And anextCallback Arguments ( Of course, you can also change koa-compose Finish what you want to be ) contextNeed to be an object Object type , Because one of the characteristics of reference data types is that they all point to the same memory address , Modify the data in a middleware , Other middleware can also respond immediately , In this way, the data can be transmitted normally
Like the following Onion Is to implement a simple version of the middleware trigger
adopt start Method , Put data in , Then a series of processing , Including log printing of data modification , All capital letters are capitalized , The first word of the sentence is capitalized
The results are as follows :
const compose = require('koa-compose')
class Onion {
constructor() {
this.middleware = []
}
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!')
this.middleware.push(fn)
return this
}
start(context) {
const fnMiddleware = compose(this.middleware)
const handleRes = () => context
return fnMiddleware(context).then(handleRes)
}
}
let onion = new Onion()
function loggerMiddleware() {
return function(ctx, next) {
console.log(` Before conversion :${
ctx.content}`)
next()
console.log(` After the transformation :${
ctx.content}`)
}
}
function titleCase(text) {
return text.trim().replace(text[0], text[0].toUpperCase())
}
function titleCaseMiddleware() {
return function(ctx, next) {
ctx.content = titleCase(ctx.content)
next()
}
}
function lowercaseMiddleware() {
return function(ctx, next) {
ctx.content = ctx.content.replace(/[A-Z]+/g, function(str) {
return titleCase(str.toLowerCase())
})
next()
}
}
onion.use(loggerMiddleware())
onion.use(lowercaseMiddleware())
onion.use(titleCaseMiddleware())
var obj = {
content: 'my name is JIOHO' }
onion.start(obj).then(res => {
console.log('================')
console.log('start Processing results :', res)
})
Research on middleware and onion model , This is just the entry level , There are more subtle processing depends on the actual scene ~
边栏推荐
- Byte order (network / host) conversion
- Video JS library uses custom components
- Sendmail dovecot mail server
- ARM指令集之批量Load/Store指令
- 网络的拓扑结构
- Design of virtual scrolling list
- go基于阿里云实现发送短信
- 【藍橋杯單片機 國賽 第十一届】
- Manuscript manuscript format preparation
- K58. Chapter 1 installing kubernetes V1.23 based on kubeadm -- cluster deployment
猜你喜欢

M-arch (fanwai 10) gd32l233 evaluation -spi drive DS1302

Unity connect to Microsoft SQLSERVER database

Les humains veulent de l'argent, du pouvoir, de la beauté, de l'immortalité, du bonheur... Mais les tortues ne veulent être qu'une tortue.

你需要社交媒体二维码的21个理由

When you have a server

ARM指令集之批量Load/Store指令

Windows10安装mysql-8.0.28-winx64

Deep learning and CV tutorial (14) | image segmentation (FCN, segnet, u-net, pspnet, deeplab, refinenet)

UML系列文章(31)体系结构建模---部署图

Windows10 install mysql-8.0.28-winx64
随机推荐
C# 37. textbox滚动条与多行
Manuscript manuscript format preparation
6.6 separate convolution
【QNX Hypervisor 2.2 用户手册】4 构建QNX Hypervisor系统
套接字实现 TCP 通信流程
Blue Bridge Cup 2015 CA provincial competition (filling the pit)
一个人必须不停地写作,才能不被茫茫人海淹没。
ARM处理器模式与寄存器
网络的拓扑结构
tensorflow 2. X multi classification confusion matrix and evaluation index calculation method (accuracy rate, recall rate, F1 score)
go基于阿里云实现发送短信
Doris记录服务接口调用情况
Windows10安装mysql-8.0.28-winx64
C# 37. Textbox scroll bar and multiline
go基于腾讯云实现发送短信
Socket Programming TCP
K52. Chapter 1: installing kubernetes v1.22 based on kubeadm -- cluster deployment
ARM指令集之Load/Store访存指令(二)
Selenium uses proxy IP
Golang Foundation (7)
