14 [Interface specification and business layering]

14 [Interface specification and business layering]

1. Interface specification - RESTful architecture

1.1 What is REST

The full name of REST is Representational State Transfer, which means expressive state transfer in Chinese. It first appeared in 2000 in the doctoral dissertation of Roy Fielding, one of the main authors of the HTTP specification. He mentioned in the paper: "The purpose of my writing this article is to understand and evaluate the architectural design of network-based application software on the premise of conforming to the architectural principles, and to obtain a powerful, good performance, and suitable for communication. REST refers to a set of architectural constraints and principles." If an architecture conforms to the constraints and principles of REST, we call it a RESTful architecture.

REST itself does not create new technologies, components or services, but the idea behind RESTful is to use the existing features and capabilities of the Web, and better use some guidelines and constraints in existing Web standards. Although REST itself is deeply influenced by Web technology, theoretically the REST architectural style is not bound to HTTP, but currently HTTP is the only instance related to REST. So the REST we describe here is also REST over HTTP.

Understanding RESTful

To understand the RESTful architecture, you need to understand what the phrase Representational State Transfer means, and what each of its words means.

Below we combine the REST principles to discuss resources, and list some key concepts and explain them from the perspectives of resource definition, acquisition, expression, association, and state transition.

  • Resources and URIs
  • Uniform Resource Interface
  • resource representation
  • Links to resources
  • state transfer

1.2 Resources and URIs

The full name of REST is representational state transfer, what kind of expression does it refer to? In fact, it refers to resources. Anything that needs to be referenced is a resource. A resource can be an entity (such as a mobile phone number) or just an abstraction (such as a value). Here are some examples of resources:

  • A user's mobile phone number
  • A user's personal information
  • GPRS packages ordered by most users
  • Dependencies between two products
  • A discount package that a certain user can handle
  • Potential value of a mobile number

To make a resource identifiable, it needs a unique identifier. In the Web, this unique identifier is URI (Uniform Resource Identifier).

URI can be regarded as both the address of the resource and the name of the resource. If some information is not represented by URI, then it cannot be regarded as a resource, but only some information of the resource. The design of URI should follow the principle of addressability, be self-describing, and need to give people an intuitive connection in form. Here, taking the github website as an example, some pretty good URIs are given:

  • https://github.com/git
  • https://github.com/git/git
  • https://github.com/git/git/blob/master/block-sha1/sha1.h
  • https://github.com/git/git/commit/e3af72cdafab5993d18fae056f87e1d675913d08
  • https://github.com/git/git/pulls
  • https://github.com/git/git/pulls?state=closed
  • https://github.com/git/git/compare/master…next

Let's take a look at some tips on URI design:

  • Use _ or - to make the URI more readable

URIs on the Web used to be cold numbers or meaningless strings, but now more and more websites use _ or - to separate some words, making URIs look more humane. For example, the well-known open source Chinese community in China adopts this style of news address, such as http://www.oschina.net/news/38119/oschina-translate-reward-plan.

  • Use / to indicate the hierarchical relationship of resources

For example, the above /git/git/commit/e3af72cdafab5993d18fae056f87e1d675913d08 represents a multi-level resource, which refers to a certain submission record of a git project of a git user. For example, /orders/2012/10 can be used to represent a resource in October 2012 order record.

  • Use? to filter resources

Many people just regard ? simply as a parameter transfer, which can easily cause the URI to be too complicated and difficult to understand. You can use? to filter resources. For example, /git/git/pulls is used to indicate all push requests of git projects, and /pulls?state=closed is used to indicate closed push requests in git projects. This kind of The URL usually corresponds to the query results or algorithm operation results of some specific conditions.

  • , or ; can be used to indicate the relationship of resources of the same level

Sometimes when we need to express the relationship of resources at the same level, we can use , or ; to split. For example, someday github can compare the difference between two random submission records of a file, perhaps using /git/git/block-sha1/sha1. However, now github uses ... to do this, such as /git/git/compare/master...next.

1.3 Uniform Resource Interface

The RESTful architecture should follow the principle of unified interface. The unified interface includes a limited set of predefined operations. No matter what kind of resources, resources are accessed through the same interface. Interfaces should use standard HTTP methods such as GET, PUT, and POST, and follow the semantics of these methods.

If the resource is exposed according to the semantics of the HTTP method, the interface will have security and idempotence characteristics. For example, GET and HEAD requests are safe, and no matter how many times the request is made, the server state will not be changed. However, GET, HEAD, PUT, and DELETE requests are all idempotent, no matter how many times the resource is operated, the result is always the same, and subsequent requests will not have more impact than the first one.

Typical usage of GET, DELETE, PUT and POST is listed below:

1.3.1 GET

  • safe and idempotent

  • get representation

  • Get representation on change (cache)

  • 200 (OK) - means sent in response

  • 204 (No Content) - resource has empty indication

  • 301 (Moved Permanently) - The URI of the resource has been updated

  • 303 (See Other) - other (eg, load balancing)

  • 304 (not modified) - the resource has not changed (cached)

  • 400 (bad request) - Refers to a bad request (eg, wrong parameters)

  • 404 (not found) - resource does not exist

  • 406 (not acceptable) - the server does not support the required representation

  • 500 (internal server error) - generic error response

  • 503 (Service Unavailable) - The server is currently unable to process the request

1.3.2 POST

  • unsafe and not idempotent

  • Create a resource with a server-managed (automatically generated) instance number

  • Create subresources

  • Partially updated resources

  • If it has not been modified, the resource is not updated (optimistic lock)

  • 200 (OK) - if the existing resource has been changed

  • 201 (created) - if a new resource was created

  • 202 (accepted) - processing request has been accepted but not yet completed (asynchronous processing)

  • 301 (Moved Permanently) - The URI of the resource has been updated

  • 303 (See Other) - other (eg, load balancing)

  • 400 (bad request) - Refers to bad request

  • 404 (not found) - resource does not exist

  • 406 (not acceptable) - the server does not support the required representation

  • 409 (conflict) - generic conflict

  • 412 (Precondition Failed) - The precondition failed (such as a conflict when performing a conditional update)

  • 415 (unsupported media type) - The received representation is not supported

  • 500 (internal server error) - generic error response

  • 503 (Service Unavailable) - The service is currently unable to process the request

1.3.3 PUT

  • unsafe but idempotent

  • Create a resource with the instance number managed by the client

  • Update resources by replacing

  • Update resource if not modified (optimistic locking)

  • 200 (OK) - if an existing resource was changed

  • 201 (created) - if a new resource was created

  • 301 (Moved Permanently) - The URI of the resource has changed

  • 303 (See Other) - other (eg, load balancing)

  • 400 (bad request) - Refers to bad request

  • 404 (not found) - resource does not exist

  • 406 (not acceptable) - the server does not support the required representation

  • 409 (conflict) - generic conflict

  • 412 (Precondition Failed) - The precondition failed (such as a conflict when performing a conditional update)

  • 415 (unsupported media type) - The received representation is not supported

  • 500 (internal server error) - generic error response

  • 503 (Service Unavailable) - The service is currently unable to process the request

1.3.4 DELETE

  • unsafe but idempotent

  • delete resource

  • 200 (OK) - resource has been deleted

  • 301 (Moved Permanently) - The URI of the resource has changed

  • 303 (See Other) - other, such as load balancing

  • 400 (bad request) - Refers to bad request

  • 404 (not found) - resource does not exist

  • 409 (conflict) - generic conflict

  • 500 (internal server error) - generic error response

  • 503 (Service Unavailable) - The server is currently unable to process the request

Let's look at some common problems in practice:

  • What is the difference between POST and PUT when used to create resources?

The difference between POST and PUT in creating resources is whether the name (URI) of the created resource is determined by the client. For example, to add a java category to my blog post, the generated path is the category name/categories/java, then the PUT method can be used. However, many people directly map POST, GET, PUT, and DELETE to CRUD. For example, this is done in a typical RESTful application implemented by rails.

I think this is because rails uses the ID generated by the server as the URI by default, and many people practice REST through rails, so it is easy to cause this misunderstanding.

  • The client does not necessarily support these HTTP methods?

This is true, especially for some older browser-based clients, which can only support GET and POST methods.

In practice, both client and server may need to make some compromises. For example, the rails framework supports passing the real request method by hiding the parameter _method=DELETE, while client-side MVC frameworks such as Backbone allow passing the _method transmission and setting the X-HTTP-Method-Override header to circumvent this problem.

  • Does the unified interface mean that methods with special semantics cannot be extended?

The unified interface does not prevent you from extending methods, as long as the method has specific and identifiable semantics for resource operations, and can maintain the uniformity of the entire interface.

For example, WebDAV has extended the HTTP method, adding methods such as LOCK and UPLOCK. The github API supports the use of the PATCH method to update the issue, for example:

PATCH /repos/:owner/:repo/issues/:number

However, it should be noted that, like PATCH, which is not an HTTP standard method, the server needs to consider whether the client can support it.

  • What does the Uniform Resource Interface mean for URIs?

The Uniform Resource Interface requires the use of standard HTTP methods to operate on resources, so the URI should only represent the name of the resource, not the operation of the resource.

In layman's terms, URIs should not be described using actions. For example, here are some URIs that do not conform to the Uniform Interface requirements:

  • GET /getUser/1
  • POST /createUser
  • PUT /updateUser/1
  • DELETE /deleteUser/1

Is it a security violation if a GET request increments the counter?

Security does not mean that requests do not have side effects. For example, many API development platforms limit request traffic. Like github, requests without authentication are limited to 60 requests per hour.

But the client does not issue these GET or HEAD requests for the sake of side effects, and the side effects are "self-assessed" by the server.

In addition, when the server is designed, the side effects should not be too large, because the client thinks that these requests will not cause side effects.

  • Is it advisable to ignore the cache directly?

Even if you use the verbs as they are intended, you can easily disable caching mechanisms. The easiest way to do this is to add such a header to your HTTP response: Cache-control: no-cache. However, at the same time you also lose support for efficient caching and revalidation (using mechanisms such as Etag).

For the client, when implementing a program client for a RESTful service, it should also make full use of the existing caching mechanism to avoid re-fetching the representation every time.

  • Is response code handling necessary?

HTTP response codes can be used in different situations, and the correct use of these status codes means that the client and server can communicate at a level with richer semantics.

For example, a 201 ("Created") response code indicates that a new resource has been created, whose URI is in the Location response header.

If you don't take advantage of the rich application semantics of HTTP status codes, you're missing opportunities to improve reusability, interoperability, and loose coupling.

If these so-called RESTful applications must pass the response entity to give error information, then SOAP is like this, and it can be satisfied.

1.4 Representation of resources

As mentioned above, the client can obtain resources through the HTTP method, right? No, to be precise, what the client obtains is only the representation of the resource. The specific presentation of resources in the outside world can have multiple forms of expression (or representation, representation), and what is transmitted between the client and the server is also the expression of the resource, not the resource itself. For example, text resources can be in html, xml, json and other formats, and pictures can be displayed in PNG or JPG.

A representation of a resource consists of data and metadata describing the data, for example, the HTTP header "Content-Type" is one such metadata attribute.

So how does the client know which form of representation the server provides?

The answer is that through HTTP content negotiation, the client can request a representation in a specific format through the Accept header, and the server can tell the client the representation of the resource through the Content-Type.

Taking github as an example, request the representation in JSON format of an organization's resources:

image-20221108163406411

1.4.1 Include the version number in the URI

Let's look at some common designs in practice:

Some APIs have a version number in the URI, for example:

  • http://api.example.com/1.0/foo
  • http://api.example.com/1.2/foo
  • http://api.example.com/2.0/foo

If we understand the version number as different expressions of resources, we should just use one URL and distinguish it through the Accept header. Taking github as an example, the complete format of its Accept is: application/vnd.github[. version].param[+json]

For the v3 version, it is Accept: application/vnd.github.v3. For the above example, the following header can be used in the same way:

  • Accept: vnd.example-com.foo+json; version=1.0
  • Accept: vnd.example-com.foo+json; version=1.2
  • Accept: vnd.example-com.foo+json; version=2.0

1.4.2 Use URI suffix to distinguish expression format

Frameworks like rails support the use of /users.xml or /users.json to distinguish between different formats. This method is undoubtedly more intuitive for the client, but it confuses the name of the resource and the expression form of the resource. Personally, I think content negotiation should be used first to distinguish presentation formats.

1.4.3 How to deal with unsupported representation formats

What should I do when the server does not support the requested presentation format? If the server does not support it, it should return an HTTP 406 response, refusing to process the request. The following uses github as an example to show the result of requesting XML representation resources:

insert image description here

1.5 Links to resources

We know that REST uses standard HTTP methods to operate resources, but it is too simple to understand it as a Web database architecture with CURD.

This anti-pattern ignores a core concept: "hypermedia as the engine of application state". What is hypermedia?

When you browse a web page, jumping from one connection to one page, and then jumping from another connection to another page, is to use the concept of hypermedia: to link resources one by one.

To achieve this goal, it is required to add links in the expression format to guide the client. In the book "RESTful Web Services", the author calls this linking feature connectivity. Let's look at some examples in detail.

The following shows the request of github to obtain the list of projects under an organization. You can see that the Link header is added to the response header to tell the client how to access the records of the next page and the last page. In the response body, use url to link the project owner and project address.

image-20221108163612111

Another example is the following example, after creating an order, guide the client how to pay through the link.

insert image description here

The above example shows how hypermedia can be used to enhance the connectivity of resources. Many people spend a lot of time looking for beautiful URIs when designing RESTful architectures, while ignoring hypermedia. Therefore, you should spend more time providing links to resource representations instead of focusing on "CRUD of resources".

1.5 State transfer

With the above foreshadowing, it will be easy to understand when discussing the state transition in REST.

However, let's first discuss the stateless communication principle in the REST principles. At first glance, it seems to be contradictory. Since there is no state, how can we talk about state transfer?

In fact, the stateless communication principle mentioned here does not mean that the client application cannot have state, but that the server should not save the client state.

1.5.1 Application status and resource status

In fact, the state should distinguish between application state and resource state. The client is responsible for maintaining the application state, while the server maintains the resource state.

The interaction between the client and the server must be stateless and include in each request all the information needed to process the request.

The server does not need to keep the application state between requests. Only when the actual request is received, the server will pay attention to the application state.

This stateless communication principle enables servers and intermediaries to understand independent requests and responses.

In multiple requests, the same client no longer needs to depend on the same server, which facilitates the realization of a highly scalable and highly available server.

But sometimes we will make a design that violates the principle of stateless communication, such as using cookies to track the session state of a certain server, such as JSESSIONID in J2EE.

This means that the cookies sent by the browser with each request are used to build the session state.

Of course, if the cookie saves some information that the server can verify without relying on the session state (such as an authentication token), such a cookie is also in line with the REST principle.

1.5.2 Application State Transfer

The state transfer here is already well understood. The "session" state is not saved on the server as a resource state, but is tracked by the client as an application state. The client application state changes under the guidance of the hypermedia provided by the server. The server tells the client which subsequent states can enter the current state through hypermedia.

These links like "next page" play the role of this advancement state-guiding you how to move from the current state to the next possible state.

2. Business layering

image-20220620210337550

The M layer can be replaced by the services folder, because the model folder stores the database model

3. Business hierarchical presentation

3.1 How to write the original code

insert image description here

config/db.config.js

const mongoose = require('mongoose')

mongoose.connect('mongodb://127.0.0.1:27017/ds')
//插入集合和数据,数据库ds2会自动创建

// 监听mongodb数据库的连接状态
// 绑定数据库连接成功事件
mongoose.connection.once('open', function () {
    
    
  console.log('连接成功')
})
// 绑定数据库连接失败事件
mongoose.connection.once('close', function () {
    
    
  console.log('数据库连接已经断开')
})

model/UserModel.js

const mongoose = require('mongoose')

const userType = new mongoose.Schema({
    
    
  username: String,
  password: String,
  age: Number,
})

const UserModel = mongoose.model('UserModel', userType, 'users')

module.exports = UserModel

app.js

var express = require('express');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

app.use('/', indexRouter);
app.use('/users', usersRouter);

module.exports = app;

routes/user.js

var express = require('express')
var router = express.Router()
const UserModel = require('../model/UserModel')

// 获取用户
router.get('/', (req, res) => {
    
    
  const {
    
     page, limit } = req.query
  UserModel
    .find({
    
     $where: 'obj.username !== ""' })
    .sort({
    
    
      age: -1,
      name: -1,
    })
    .then(data => res.send(data))
})

// 添加用户
router.post('/', function (req, res, next) {
    
    
  const {
    
     username, password, age } = req.body
  new UserModel({
    
     username, password, age }).save((err, docs) => {
    
    
     res.send({
    
    
       code: 200,
       data: {
    
    
         id: docs._id,
       },
     })
   })
})

// 修改用户
router.put('/:id', function (req, res, next) {
    
    
  const {
    
     username, password, age } = req.body
  UserModel
    .updateOne(
      {
    
    
        _id: req.params.id,
      },
      {
    
    
        username: '更新',
      },
    )
    .then(data => {
    
    
      res.send({
    
    
        ok: 1,
      })
    })
})

// 删除用户
router.delete('/:id', function (req, res, next) {
    
    
  const {
    
     username, password, age } = req.body
  UserModel
    .deleteOne({
    
    
      _id: req.params.id,
    })
    .then(data => {
    
    
      res.send({
    
    
        ok: 1,
      })
    })
})
module.exports = router

3.2 Modify the code with business layering

image-20221108162652289

config/db.config.js, model/UserModel.js, app.jsunchanged

router/user.js

var express = require('express')
var router = express.Router()
const userController = require('../controllers/userController')

router.get('/', userController.getUser)

router.post('/', userController.addUser)

router.put('/:id', userController.updateUser)

router.delete('/:id', userController.deleteUser)

module.exports = router

controllers/userController.js

const userService = require('../services/userService')

const userController = {
    
    
  async getUser(req, res, next) {
    
    
    const {
    
     page, limit } = req.query
    let data = await userService.getUser(page, limit)
    res.send(data)
  },
  async addUser(req, res, next) {
    
    
    const {
    
     username, password, age } = req.body
    let data = await userService.addUser({
    
     username, password, age })
    res.send(data)
  },
  async updateUser(req, res, next) {
    
    
    let data = await userService.updateUser(req.params.id)
    res.send(data)
  },
  async deleteUser(req, res, next) {
    
    
    let data = await userService.deleteUser(req.params.id)
    res.send(data)
  },
}

module.exports = userController

services/userService.js

const userModel = require('../model/userModel')

const userService = {
    
    
  getUser(page, limit) {
    
    
    return userModel
      .find({
    
    }, {
    
     _id: 0 })
      .sort({
    
    
        age: -1,
      })
      .skip((page - 1) * limit)
      .limit(limit)
  },
  addUser({
     
      username, password, age }) {
    
    
    return userModel.create({
    
    
      username,
      password,
      age,
    })
  },
  updateUser(_id) {
    
    
    return userModel.updateOne(
      {
    
    
        _id,
      },
      {
    
    
        username: '更新',
      },
    )
  },
  deleteUser(_id) {
    
    
    return userModel.deleteOne({
    
    
      _id,
    })
  },
}

module.exports = userService

Guess you like

Origin blog.csdn.net/DSelegent/article/details/128130113