The author:Gavin, prohibit reprinting without authorization.

preface

In front and back end data interaction, the most common data interaction modes in China are as follows:

Gateway-to-microservices typically interact using RPC, which is not covered in this article.

Domestic companies usually use Rest for the interaction between front-end and gateway, while many foreign companies use GraphQL. In terms of technical implementation, these two ways have their own advantages and disadvantages, and are not difficult.

For developers, if there is no need to distinguish between client and server, the original set of code can be called each other, but after the distinction, they have to use URL + GET/POST/DELETE + query/body/params for data interaction, which will generate a lot of repeated logic and code at the front and back ends, such as: Of course, we have to define a set of interface specifications that have nothing to do with JS/TS. If React and Vue are used, we may also introduce Redux, Vuex and other state management libraries, which are a little tedious for small projects.

The problem

Is there a way to reuse back-end code, logic, like RPC calls, or even manage state?

The answer, of course, is yes.

Traditional solutions

Server side rendering

Taking next as an example (native server rendering or NUxt is similar), you can write gateway code to node, which does two things:

  1. Gateway layer: RPC communication with the service layer
  2. Route differentiation: Routes to next are identified and handed to next for rendering, routes to interfaces are identified and handed to corresponding controllers for processing

Sample pseudocode

const Koa = require('koa');
const next = require('next');
// const config = require('xxx'); // Basic configuration
// const log = require('xxx'); / / log

if (!module.parent) {
  nextApp.prepare()
    .then(() = > {
      nextApp.setAssetPrefix('/next');
      app.use(router.routes());
      app.use(router.allowedMethods());
      app.listen(config[env].port, config[env].host, () = > {
        log.info(`API server listening on ${config[env].host}:${config[env].port}, in ${env}`);
      });
    });
} else {
  app.use(router.routes());
  app.use(router.allowedMethods());
}
Copy the code

The use of server-side rendering can solve the problem of code reuse, the front and back end code can call public functions, enumeration, etc., but this is not really reuse, in the process of the first screen dynamic GENERATION of JS or will extract the dependent code dynamically compiled into the first screen dynamic generation of JS. Of course, in traditional development mode, you still have to use the traditional HTTP interface for communication.

In server-side rendering libraries such as Next/Nuxt, data interaction over HTTP is more cumbersome than traditional Ajax because of the need to differentiate the context in which the request is currently being executed.

Sample pseudocode

const request = method= > async (path, data = {}, req = {}) => {
  const keys = Object.keys(data);
  const config = {
    method,
    headers: {
      'Content-Type': 'application/json'}};let baseUrl;
  if (typeof window= = =void 0) {
    / / the server
    config.headers['User-Agent'] = req.headers['user-agent'];
    config.headers['Cookie'] = req.headers['cookie'];
    baseUrl = 'http://127.0.0.1:3000/api'; // The current Node service listens on port 3000
  } else {
    / / the client
    config.credentials = 'include';
    baseUrl = '/api';
  }
  const api = baseUrl + path;
  const res = await (async() = > {if (method === 'GET') {
      return! keys.length ?await fetch(api, config) : await fetch(`${api}?${ _.compact(keys.map(key => data[key] ? `${key}=${data[key]}` : ' ')).join('&')}`, config)
    } else {
      return awaitfetch(api, { ... config,body: JSON.stringify(data),
      })
    }
  })().catch(e= > {
    log.error(e);
  });

  const resJson = await res.json();
  return resJson
};
Copy the code

For state management, server-side rendering can also introduce redux, VUex and other similar state management libraries, but they are a little more complicated:

  1. The first screen initializes the store;
  2. Store Synchronization between the front and back ends

Single-page application + Node

In addition to the top server rendering, pure single-page application + Node can also achieve code logic reuse, which is similar to the principle of the top server rendering, but next has a dynamic first screen generation process, while single-page application all code is static, of course, there is no real sense of code logic sharing. Mainly through webpack and other tools to pack dependencies into the front-end static resources, through Node to start a static resource access directory, or to the third-party static resource management center, hang a CDN.

A better way:Layr

Introduction to the

Layr is an object-oriented JS/TS library for RPC-like communication.

How to use

  1. The back end consists of one or more classes, each of which exposes some attributes and methods to the front end;
  2. The front end generates a proxy for the back-end class through which it communicates.

The principle of

On the surface, Layr is similar to Java RMI, but the principle is very different:

  1. Layr is not a distributed object, the backend is stateless, there is no shared object in the stack;
  2. Layr does not involve any template code, code generation, configuration files, etc.
  3. Use the Deepr protocol

The sample

The back-end

import {
  Component,
  primaryIdentifier,
  attribute,
  method,
  expose
} from '@layr/component';
import {ComponentHTTPServer} from '@layr/component-http-server';

class Counter extends Component {
  @expose({get: true.set: true}) @primaryIdentifier() id;
  @expose({get: true.set: true}) @attribute() value = 0;
  @expose({call: true}) @method() increment() {
    this.value++; }}const server = new ComponentHTTPServer(Counter, {port: 3210});
server.start();
Copy the code

The front end

import {ComponentHTTPClient} from '@layr/component-http-client';

(async() = > {// Establish a connection
  const client = new ComponentHTTPClient('http://localhost:3210');
  const Counter = await client.getComponent();
  
  // Data subscription example
  class ExtendedCounter extends Counter {
    async increment() {
      await super.increment();
      if (this.value === 3)
        console.log('State changed, trigger XXX'); }}}// Get and modify data
  const counter = new Counter(); // new ExtendedCounter();
  console.log(counter.value); / / = > 0
  await counter.increment();
  console.log(counter.value); / / = > 1
  await counter.increment();
  console.log(counter.value); / / = > 2}) ();Copy the code

conclusion

Pay attention to

This paper only provides a new idea of front and back end data interaction. In my opinion, Layr is currently only suitable for small projects with rapid iteration. For large projects, whether Layr or GraphQL is used, the resource requirements on the server (Node) will be high.

Layr is designed with the idea of object-oriented, which is a bit rude for students who are used to react functional programming mode. Layr is more friendly to students who are used to Java development mode.

Unlike the traditional Node/PHP+ template format, the front and back ends of Layr are completely independent and can be deployed independently.

A link to the

  • Layr
  • Good Bye Web APIs
  • More Use Cases