Discussion on The Architectural Pattern of Web Framework (JavaScript Language)

Before writing dry goods, I want to explore (Qiang) discuss (DIao) two questions, the limitations of the model? What’s the use of patterns?

Recently, I was inspired by an article by Xu Laixi in zhihu’s answer to “What is the relationship between Philosophy and Science?” , the whole article is longer, here is an excerpt of the point I want to draw out:

As an empirical epistemology, science has the great defect of empiricism: it can never produce absolutely correct truth. That’s the nature of induction. And it’s important to note that induction is not unique.

To take a simple example, let’s imagine a world that looks like this:

Scientists soon came up with two kinds of generalizations:

  • All the frogs in the world wear glasses
  • All the people in the world who wear glasses are frogs

In the absence of more information, how do we choose the right theory? The answer is no choice.

As an example of a pattern, Scott Wlaschin in Functional Programming Design Patterns compares common object-oriented Patterns and principles to their equivalents in Functional Programming languages:

Which programming paradigm is more advanced, OOP or FP? The answer, again, is no choice. Different hypotheses and theories can only be used at different times to explain problems. Xu Laixi’s article argues that science has to some extent traded practicality for consistency and consistency for consistency. Science pursues utility and instrumentalism (pragmatism and instrumentalism). When I finished reading Xu Laoxi’s article, I was ecstatic. I always felt fear and disgust for the fickleness and inconsistency of programming technology theory. In fact, it is just an inevitable process of the development of empirical science.

So I want to introduce the first idea:

  • Pattern is a set of schemes based on a specific background and generality, which is by no means the truth.

Knowing this can help move from pattern fetishism to exploring its usefulness and instrumentality, which brings me to my second question: What are patterns good for?

Reading philosophy articles without writing code is no accident. Before writing this article, I was wondering if patterns still make sense in the context of JavaScript, a dynamic, multi-paradigm, single-threaded, event-based I/O language, even in today’s world. Obviously, I’m not the only one who thinks this way. There is also an excellent in-depth article, “Is the design pattern proposed by GoF 20 years ago still relevant to this era?” . This article is an excerpt from GoF (also known as the Gang of Four, Erich Gamma, Richard Helm, Ralph Johnson & John Vlissides) ‘s book Design Patterns:

The book’s actual value may be debatable. After all, it doesn’t come up with any new algorithms or programming techniques. Nor does it offer any rigorous systems design methodology or new design development theory — it’s just a review of existing design efforts. You can certainly think of it as a good set of tutorials, but it certainly doesn’t do much for experienced object-oriented designers.

In other words, patterns are clearly useless.

Moreover, the article also lists many abuses caused by the abuse of the model, which can be described as a wake-up call.

But… The term pattern still pops up, and we use it a lot today. Why is that? GoF’s book on actual early Design Patterns predicts:

“Design patterns provide designers with a common vocabulary to communicate, document, and explore design solutions. Design patterns allow us to talk at a high level of abstraction, rather than design annotations or programming languages, which greatly reduces system complexity. Design patterns raise the level of entry points for design and design discussions with colleagues.” (Page 389)

In short, patterns facilitate communication and raise the level of abstraction to consider the problem.

This makes a lot of sense. Imagine that without the MVC architecture pattern, probably all Web frameworks would inevitably implement a set of solutions to almost the same problem, but with different names and different documentation. When you look at the API interface of a new framework document and read it from start to finish, you suddenly realize. Isn’t it similar to XXX in the framework used before, such a programming world is hell. Fortunately, thanks to computer scientists’ continued abstraction of problems and solutions into patterns, today’s highly complex computer science can be properly layered and adapted, greatly simplifying the cost of learning and communication.

In order to thank patterns, it’s time to take a look at three main architectural patterns: Middleware, MVC, and DI.

Middleware Pattern

For those of you who have done Node.js server development, consider the following Web application scenarios:

In a simple HTTP request response cycle, the following conditions are processed,

  • Record start time
  • User authentication needs to be verified.
  • Parse the cookie and load the body
  • Returns different service processing results based on routes
  • If no route is matched, page 404 is returned
  • log
  • Keep track of the total time spent
  • Handle exceptions and display pages (development environment)

Some processes decide whether to continue with the subsequent coarse grain based on success, some processes generate additional data, and some require the interception of the beginning and end of certain processes. Finally, exception handling and logging must be performed.

The common solution is to use nested conditional judgments combined with control statements such as try catch finally return, but such schemes can lead to code fragmentation and copy-and-paste coding styles because control flow and logic are coupled together. The ideal scenario would be as follows:

  • Centralized control flow
  • Decoupled processing modules (reusability)
  • Declarative, configurable services (configuration independent of code)

The Intercepting Filter model has a long history. A long time ago, J2EE summarized the Intercepting Filter model. If you are interested, check out this article (LUN) chapter (wen).

public class DebuggingFilter implements Processor {
  private Processor target;
  public DebuggingFilter(Processor myTarget) {
    target = myTarget;
  }
  public void execute(ServletRequest req, 
  ServletResponse res) throws IOException, 
    ServletException    {
    // preprocess
    target.execute(req, res);
    // post-process
  }
}
Copy the code

This is very similar to the middleware patterns of Express and Koa, but the resulting enterprise-level code is cumbersome and has many limitations due to the inherent characteristics of static languages. The main problem is the difficulty of reusing and sharing data between processing modules because ServletRequest ServletResponse cannot add attributes dynamically. As a result, JavaEE places many limitations on the applicability of this pattern, including separation from the core processing logic.

In the world of dynamic languages, it is very easy to add data to REq and RES (based on convention), because without many of the “constraints” of the OOP world, Node.js implementations are often more elegant and generic.

Express Middleware Pattern

Express implements today’s widely accepted Middleware pattern. Middleware means a function that executes between a request and a response (to distinguish between another middleware) and is signed as follows:


var express = require('express');
var app = express();

Copy the code

This pattern contains a set of declarative routing rules and the next signature on the Middleware function, which together form the control flow of the middleware pattern, as shown below:

The core components of this pattern are not middleware logic such as permissions, parsing, etc., but routing judgment, next, and interrupt response (validation failure, parsing failure), which act as middleware execution control and decouple specific processing logic, making it easier to write generic fine-grained middleware. Express has powerful declarative routing built into it, and the separation of routing from Middleware is arguably one of its most successful designs.

However, in a slightly more complex business, such as a website with a management side and a client side, the two sides are equivalent to a separate app. Express 4.0 provides a very powerful Router. The Router extends the chain decision to a tree decision, allowing Express to better support large projects.

/* bird.js */ var router = require('express') var router = express.router () router.get('/', function (req), Module. Exports = router /* / var Birds = require('./ Birds ') //... app.use('/birds', birds)Copy the code

Koa asynchronous middleware model

Koa’s asynchronous middleware pattern, the Onion model, has middleware functions that return promises compared to Express, supports async/await, and can easily implement pre – and post-processing. There is no doubt that this mode is more advanced, and some of the interception processing logic that is difficult to implement in Express, such as exception handling and statistical timing, can be handled with a middleware in Koa. Unfortunately, Koa itself provides only minimal encapsulation of Http modules and onion models.

I’m optimistic about Koa in the future, and Express is aware of this, and they plan to add Promise support in version 5.0. However, as an established and fully ecological framework, the challenges to overcome are far more than technical. We haven’t seen 5.x announce support for Promise yet, so let’s wait and see.

The MVC pattern

Do we also need to introduce MVC mode? We talk about MVC every day, regardless of the front and back end framework, say MVC, make eye contact, and basically make sure that the other party understands you.

The fact is that front-end frameworks are no longer suitable for MVC discussion. This model has been used in various frameworks and scenarios since 1979 as the all-essential oil model, which carries too much historical baggage. You can read Winter’s article on the evolution of UI architecture design. I think there’s hope, but when we talk about the front-end framework we’ll call it MV mode, which is model and view separation.

The MVC pattern we are going to talk about today refers to the server-side (back-end) MVC pattern, whose definition has stood the test of time and practice and is highly consistent across many enterprise-level Web framework implementations. Here are the scenarios:

If your site has a few simple pages and all the logic is written in the Controller, that’s fine. And then as the site grows rapidly, you realize,

  • Many pages have consistent views, but inconsistent data models behind them. For example, almost no view or component on the site is unique, such as tables, drop-down boxes, etc.
  • The data model is the same in many pages, but the views presented are inconsistent. For example: support both PC and mobile terminals, internationalization and localization.

Let’s do a mathematical model to simulate the extreme case, so you can easily see the problem

Given that the left-hand side is what our system ends up looking like, and it just happens to be the inner product of M (model) and V(view), we prefer the right-hand side because it’s more concise and not repetitive. The inner product operation here you can think of as a controller, and it’s not really quite so coincidental, but the benefits of separating the model and the view to improve code reuse and reduce design complexity are obvious, a more general expression

There are one-way links between the model view and the Controller, so the behavior of the whole system is very controllable and easy to test. The separation of routes is to emphasize that Router and Controller are two concepts, and Router is just a trigger (or provides a mapping relationship). When writing tests, We can also skip the Router and call the Controller separately.

ThinkJS 3.0 is a very useful enterprise Web framework. The latest version of ThinkJS integrates a lot of best practices and complete documentation. Highly recommended for both learning and enterprise development. ThinkJS also implements the following pattern.

DI dependency injection mode

Or the scenario, if the server needs to realize session, considering the cost and number of users, it is enough for a single server to save the files. In the later period, if a large number of users need scale-out, session will be realized as centralized Redis service.

Our system design objectives are:

  • There is no need to modify the business logic code to implement the substitution
  • There is no need to focus on service creation and life cycle

DI Dependency injection is one of the well-known IoC Inversion of Control (IoC) design principles to solve this kind of system scalability problem.

The basic idea of DI is that our code can’t rely on a specific service. We need to generalize a set of abstract interfaces, with business implementations relying on interfaces and services implementing interfaces. Finally, the framework is responsible for creating and providing instances of interfaces.

The IoC container or IoC framework here reads configuration files at startup, and creates instances to provide to users as needed during operation. In static languages such as Java and c#, reflection and other advanced syntax are required, while JavaScript itself is dynamic and its interface is based on conventions. And the way they use it is more flexible. For example, extend and Adapter in ThinkJS 3.0 can be understood as interfaces and implementations, as shown below:

That’s called EXTEND because the framework will inject interfaces directly into controller or think objects. The advantage is that it is more convenient to use, but the disadvantage is that different extend needs to be agreed not to be the same name.

The last

The benefits of the three architectural patterns described in this article are that you will find that almost all Web framework implementations are similar. The meaning of mode is similar to IoC. I focus on abstraction and interface, and smooth out the details of specific language features to help us better learn, communicate and think.