web全栈架构师所需技术栈_统一架构–一种构建全栈应用程序的简单方法

web全栈架构师所需技术栈

Modern full-stack apps – like single-page apps or mobile apps – usually have six layers

现代的全栈应用程序(如单页应用程序或移动应用程序)通常具有六层

  • data access

    资料存取
  • backend model

    后端模型
  • API server

    API服务器
  • API client

    API客户端
  • frontend model

    前端模型
  • and user interface.

    和用户界面。

By architecting in this way, you can achieve some characteristics of a well-designed application, such as separation of concerns or loose coupling.

通过以这种方式进行架构,您可以实现精心设计的应用程序的某些特征,例如关注点分离松散耦合

But this does not come without drawbacks. It usually comes at the cost of other important characteristics, like simplicity, cohesion, and agility.

但这并非没有缺点。 它通常以其他重要特征为代价,例如简单性, 内聚性和敏捷性。

It seems we can't have it all. We have to compromise.

似乎我们无法拥有全部。 我们必须妥协。

The problem is that developers usually build each layer as a completely different world on its own.

问题在于,开发人员通常将每个层单独构建为一个完全不同的世界。

Even if you implement the layers with the same language, they cannot communicate with one another very easily.

即使您使用相同的语言实现这些图层,它们也无法非常轻松地相互通信。

You would need a lot of glue code to connect them all, and the domain model gets duplicated across the stack. As a result, your development agility suffers dramatically.

您将需要很多粘合代码才能将它们全部连接起来,并且域模型会在堆栈中重复。 结果,您的开发敏捷性遭受了巨大损失。

For example, adding a simple field to a model often requires modifying all the layers of the stack. This can feel a bit ridiculous.

例如,向模型添加简单字段通常需要修改堆栈的所有层。 这可能有点荒谬。

Well, I've been thinking a lot about this problem recently. And I believe I've found a way out.

好吧,最近我一直在考虑这个问题。 而且我相信我已经找到了出路。

Here's the trick: for sure, the layers of an application must be "physically" separated. But they don't need to be "logically" separated.

这就是窍门:当然,应用程序的各层必须“物理地”分开。 但是,它们不必在逻辑上分开。

统一架构 (The Unified Architecture)

In object-oriented programming, when we use inheritance, we get some classes that can be seen in two ways: physical and logical. What do I mean by that?

在面向对象的编程中,当我们使用继承时,我们可以通过两种方式看到一些类:物理类和逻辑类。 那是什么意思

Let's imagine we have a class B that inherits from a class A. Then, A and B can be seen as two physical classes. But logically, they are not separated, and B can be seen as a logical class that composes the properties of A with its own properties.

假设我们有一个继承自A类的B类。 那么, AB可以看作是两个物理类。 但是从逻辑上讲,它们不是分开的,并且B可以看作是逻辑类,它将A的属性与自身的属性组成。

For example, when we call a method in a class, we don't have to worry if the method is implemented in this class or a parent class. From the caller perspective, there is only one class to worry about. Parent and child are unified into a single logical class.

例如,当我们在类中调用一个方法时,我们不必担心该方法是在此类还是在父类中实现的。 从呼叫者的角度来看,只需要担心一个类。 父级和子级统一为一个逻辑类。

How about applying the same approach to the layers of an application? Wouldn't it be great if, for example, the frontend could somehow inherit from the backend?

如何将相同的方法应用于应用程序的各层? 例如,如果前端可以以某种方式从后端继承,那不是很好吗?

Doing so, frontend and backend would be unified into a single logical layer. And that would remove all communication and sharing issues. Indeed, backend classes, attributes, and methods would be directly accessible from the frontend.

这样做,前端和后端将统一为一个逻辑层。 这样就可以消除所有交流和共享问题。 实际上,可以从前端直接访问后端类,属性和方法。

Of course, we don't usually want to expose the whole backend to the frontend. But the same goes for class inheritance, and there is an elegant solution that is called "private properties". Similarly, the backend could selectively expose some attributes and methods.

当然,我们通常不希望将整个后端公开给前端。 但是类继承也是如此,并且有一个优雅的解决方案称为“私有属性”。 同样,后端可以有选择地公开一些属性和方法。

Being able to grasp all the layers of an application from one single unified world is not a small deal. It changes the game completely. It is like going from a 3D world to a 2D world. Everything gets a lot easier.

能够从一个统一的世界掌握应用程序的所有层并不是一件容易的事。 它完全改变了游戏。 就像从3D世界到2D世界一样。 一切变得容易得多。

Inheritance is not evil. Yes, it can be misused, and in some languages, it can be pretty rigid. But when properly used, it is an invaluable mechanism in our toolbox.

继承不是邪恶的 。 是的,它可能被滥用,并且在某些语言中,它可能非常僵化。 但是,如果使用得当,它是我们工具箱中的宝贵机制。

We have a problem, though. As far as I know, there is no language allowing us to inherit classes across multiple execution environments. But we are programmers, aren't we? We can build everything we want, and we can extend the language to provide new capabilities.

但是,我们有一个问题。 据我所知,没有语言允许我们在多个执行环境中继承类。 但是我们是程序员,不是吗? 我们可以构建所需的一切,并且可以扩展语言以提供新功能。

But before we get to that, let's break down the stack to see how each layer can fit in a unified architecture.

但是在开始之前,让我们分解一下堆栈,看看每一层如何适合统一架构。

资料存取 (Data Access)

For a majority of applications, the database can be abstracted using some sort of ORM. So, from the developer perspective, there is no data access layer to worry about.

对于大多数应用程序,可以使用某种ORM来抽象数据库。 因此,从开发人员的角度来看,无需担心数据访问层。

For more ambitious applications, we might have to optimize database schemas and requests. But we don't want to clutter the backend model with these concerns, and this is where an additional layer may be appropriate.

对于更具雄心的应用程序,我们可能必须优化数据库架构和请求。 但是,我们不想因这些问题而使后端模型混乱不堪,因此这里可能需要附加一层。

We build a data access layer to implement the optimization concerns, and this usually happens late in the development cycle, if it ever happens.

我们构建一个数据访问层来实现优化问题,并且通常会在开发周期的后期发生(如果有的话)。

Anyway, if we need such a layer, we can build it later. With cross-layer inheritance, we can add a data access layer on top of the backend model layer with almost no changes to the existing code.

无论如何,如果我们需要这样的层,我们可以稍后构建它。 通过跨层继承,我们可以在后端模型层之上添加一个数据访问层,而几乎无需更改现有代码。

后端模型 (Backend Model)

Typically, a backend model layer handles the following responsibilities:

通常,后端模型层处理以下职责:

  • Shaping the domain model.

    塑造领域模型。
  • Implementing business logic.

    实施业务逻辑。
  • Handling the authorization mechanisms.

    处理授权机制。

For most backends, it's fine to implement them all in a single layer. But, if we want to handle some concerns separately, for example, we want to separate the authorization from the business logic, we can implement them in two layers that inherit from each other.

对于大多数后端,最好在一个层中实现它们。 但是,如果我们想分别处理某些问题,例如,要将授权与业务逻辑分开,则可以在彼此继承的两层中实现它们。

API层 (API Layers)

To connect the frontend and the backend, we usually build a web API (REST, GraphQL, etc.), and that complicates everything.

为了连接前端和后端,我们通常构建一个Web API(REST,GraphQL等),这会使一切变得复杂。

The web API must be implemented on both sides: an API client in the frontend and an API server in the backend. That's two extra layers to worry about, and it usually leads to duplicate the whole domain model.

Web API必须在两侧都实现:前端的API客户端和后端的API服务器。 这是另外两个需要担心的层,通常会导致复制整个域模型。

A web API is nothing more than glue code, and it is a pain in the ass to build. So, if we can avoid it, that's a massive improvement.

Web API无非就是胶水代码,并且很难构建。 因此,如果我们能避免它,那将是一个巨大的进步。

Fortunately, we can take advantage of cross-layer inheritance again. In a unified architecture, there is no web API to build. All we have to do is to inherit the frontend model from the backend model, and we are done.

幸运的是,我们可以再次利用跨层继承。 在统一体系结构中,没有要构建的Web API。 我们要做的就是从后端模型继承前端模型,然后就完成了。

However, there are still some good use cases for building a web API. That's when we need to expose a backend to some third-party developers, or when we need to integrate with some legacy systems.

但是,仍然存在一些构建Web API的好用例。 那时候我们需要向某些第三方开发人员公开后端,或者当我们需要与某些旧系统集成时。

But let's be honest, most applications don't have such a requirement. And when they do, it is easy to handle it afterward. We can simply implement the web API into a new layer that inherits from the backend model layer.

但是说实话,大多数应用程序都没有这样的要求。 而当他们这样做时,以后就很容易处理它。 我们可以简单地将Web API实现到继承自后端模型层的新层中。

Further information on this topic can be found in this article.

有关此主题的更多信息,请参见本文

前端模型 (Frontend Model)

Since the backend is the source of truth, it should implement all the business logic, and the frontend should not implement any. So, the frontend model is simply inherited from the backend model, with almost no additions.

由于后端是事实的源头,因此它应该实现所有业务逻辑,而前端则不应实现任何逻辑。 因此,前端模型只是从后端模型继承而来,几乎没有添加。

用户界面 (User Interface)

We usually implement the frontend model and the UI in two separate layers. But as I showed in this article, it is not mandatory.

我们通常在两个单独的层中实现前端模型和UI。 但是,正如我在本文中所展示的那样,这不是强制性的。

When the frontend model is made of classes, it is possible to encapsulate the views as simple methods. Don't worry if you don't see what I mean right now, it will become clearer in the example later on.

当前端模型由类组成时,可以将视图封装为简单方法。 如果您现在不明白我的意思,请不要担心,在后面的示例中它将变得更加清楚。

Since the frontend model is basically empty (see above), it is fine to implement the UI directly into it, so there is no user interface layer per se.

由于前端模型基本上是空的(请参见上文),可以直接在其中实现UI,所以本身就没有用户界面层。

Implementing the UI in a separate layer is still needed when we want to support multiple platforms (e.g., a web app and a mobile app). But, since it is just a matter of inheriting a layer, that can come later in the development roadmap.

当我们要支持多个平台(例如,Web应用程序和移动应用程序)时,仍然需要在单独的层中实现UI。 但是,由于仅是继承层的问题,因此可以在开发路线图的后面进行。

放在一起 (Putting Everything Together)

The unified architecture allowed us to unify six physical layers into one single logical layer:

统一的体系结构使我们可以将六个物理层统一为一个逻辑层:

  • In a minimal implementation, data access is encapsulated into the backend model, and the same goes for UI that is encapsulated into the frontend model.

    在最小的实现中,数据访问被封装到后端模型中,UI封装到前端模型中也是如此。
  • The frontend model inherits from the backend model.

    前端模型继承于后端模型。
  • The API layers are not required anymore.

    不再需要API层。

Again, here's what the resulting implementation looks like:

同样,这是结果实现的样子:

That's pretty spectacular, don't you think?

那真是太壮观了,您不觉得吗?

联络 (Liaison)

To implement a unified architecture, all we need is cross-layer inheritance, and I started building Liaison to achieve exactly that.

为了实现统一的体系结构,我们所需要的只是跨层继承,而我开始构建Liaison来实现这一点。

You can see Liaison as a framework if you wish, but I prefer to describe it as a language extension because all its features lie at the lowest possible level — the programming language level.

如果愿意,您可以将Liaison视为框架,但是我更喜欢将其描述为语言扩展,因为它的所有功能都位于最低的级别-编程语言级别。

So, Liaison does not lock you into a predefined framework, and a whole universe can be created on top of it. You can read more on this topic in this article.

因此,Liaison不会将您锁定在预定义的框架中,而是可以在其之上创建整个Universe。 您可以在本文中阅读有关此主题的更多信息。

Behind the scene, Liaison relies on an RPC mechanism. So, superficially, it can be seen as something like CORBA, Java RMI, or .NET CWF.

在后台,Liaison依赖于RPC机制。 因此,从表面上看,它可以看作是CORBAJava RMI.NET CWF之类的东西

But Liaison is radically different:

但是联络完全不同:

  • It is not a distributed object system. Indeed, a Liaison backend is stateless, so there are no shared objects across layers.

    它不是分布式对象系统 。 实际上,联络后端是无状态的,因此没有跨层的共享对象。

  • It is implemented at the language-level (see above).

    它是在语言级别实现的(请参见上文)。
  • Its design is straightforward and it exposes a minimal API.

    它的设计简单明了,并且公开了最少的API。
  • It doesn't involve any boilerplate code, generated code, configuration files, or artifacts.

    它不涉及任何样板代码,生成的代码,配置文件或工件。
  • It uses a simple but powerful serialization protocol (Deepr) that enables unique features, such as chained invocation, automatic batching, or partial execution.

    它使用简单但功能强大的序列化协议( Deepr ),该协议可启用独特功能,例如链式调用,自动批处理或部分执行。

Liaison starts its journey in JavaScript, but the problem it tackles is universal, and it could be ported to any object-oriented language without too much trouble.

Liaison从JavaScript开始,但是它解决的问题是普遍的,它可以移植到任何面向对象的语言而不会带来太多麻烦。

你好柜台 (Hello Counter)

Let's illustrate how Liaison works by implementing the classic "Counter" example as a single-page application.

让我们通过将经典的“ Counter”示例实现为单页应用程序来说明Liaison的工作方式。

First, we need some shared code between the frontend and the backend:

首先,我们需要在前端和后端之间共享一些代码:

// shared.js

import {Model, field} from '@liaison/liaison';

export class Counter extends Model {
  // The shared class defines a field to keep track of the counter's value
  @field('number') value = 0;
}

Then, let's build the backend to implement the business logic:

然后,让我们构建后端以实现业务逻辑:

// backend.js

import {Layer, expose} from '@liaison/liaison';

import {Counter as BaseCounter} from './shared';

class Counter extends BaseCounter {
  // We expose the `value` field to the frontend
  @expose({get: true, set: true}) value;

  // And we expose the `increment()` method as well
  @expose({call: true}) increment() {
    this.value++;
  }
}

// We register the backend class into an exported layer
export const backendLayer = new Layer({Counter});

Finally, let's build the frontend:

最后,让我们构建前端:

// frontend.js

import {Layer} from '@liaison/liaison';

import {Counter as BaseCounter} from './shared';
import {backendLayer} from './backend';

class Counter extends BaseCounter {
  // For now, the frontend class is just inheriting the shared class
}

// We register the frontend class into a layer that inherits from the backend layer
const frontendLayer = new Layer({Counter}, {parent: backendLayer});

// Lastly, we can instantiate a counter
const counter = new frontendLayer.Counter();

// And play with it
await counter.increment();
console.log(counter.value); // => 1

What's going on? By invoking counter.increment(), we got the counter's value incremented. Notice that the increment() method is neither implemented in the frontend class nor in the shared class. It only exists in the backend.

这是怎么回事? 通过调用counter.increment() ,我们使计数器的值增加了。 请注意,既未在前端类中也未在共享类中实现increment()方法。 它仅存在于后端。

So, how is it possible that we could call it from the frontend? This is because the frontend class is registered in a layer that inherits from the backend layer. So, when a method is missing in the frontend class, and a method with the same name is exposed in the backend class, it is automatically invoked.

那么,我们怎么可能从前端调用它呢? 这是因为前端类在从后端层继承的层中注册。 因此,如果前端类中缺少某个方法,并且后端类中公开了具有相同名称的方法,则会自动调用该方法。

From the frontend point of view, the operation is transparent. It doesn't need to know that a method is invoked remotely. It just works.

从前端的角度来看,该操作是透明的。 不需要知道某个方法是远程调用的。 它就是有效的。

The current state of an instance (i.e., counter's attributes) is automatically transported back and forth. When a method is executed in the backend, the attributes that have been modified in the frontend are sent. And inversely, when some attributes change in the backend, they are reflected in the frontend.

实例的当前状态(即counter的属性)会自动来回传输。 在后端执行方法时,将发送在前端已修改的属性。 相反,当某些属性在后端发生更改时,它们会反映在前端中。

Note that in this simple example, the backend is not exactly remote. Both the frontend and the backend run in the same JavaScript runtime. To make the backend truly remote, we can easily expose it through HTTP. See an example here.

请注意,在这个简单的示例中,后端并不完全是远程的。 前端和后端都在同一JavaScript运行时中运行。 为了使后端真正处于远程状态,我们可以轻松地通过HTTP公开它。 在这里查看示例

How about passing/returning values to/from a remotely invoked method? It is possible to pass/return anything that is serializable, including class instances. As long as a class is registered with the same name in both the frontend and the backend, its instances can be automatically transported.

向/从远程调用的方法传递/返回值如何? 可以传递/返回可序列化的任何内容,包括类实例。 只要一个类在前端和后端都用相同的名称注册,就可以自动传输其实例。

How about overriding a method across the frontend and the backend? It is no different than with regular JavaScript – we can use super. For example, we can override the increment() method to run additional code in the context of the frontend:

如何在前端和后端中覆盖方法? 与普通JavaScript没什么不同-我们可以使用super 。 例如,我们可以覆盖increment()方法以在前端上下文中运行其他代码:

// frontend.js

class Counter extends BaseCounter {
  async increment() {
    await super.increment(); // Backend's `increment()` method is invoked
    console.log(this.value); // Additional code is executed in the frontend
  }
}

Now, let's build a user interface with React and the encapsulated approach shown earlier:

现在,让我们使用React和前面显示的封装方法来构建用户界面:

// frontend.js

import React from 'react';
import {view} from '@liaison/react-integration';

class Counter extends BaseCounter {
  // We use the `@view()` decorator to observe the model and re-render the view when needed
  @view() View() {
    return (
      <div>
        {this.value} <button onClick={() => this.increment()}>+</button>
      </div>
    );
  }
}

Finally, to display the counter, all we need is:

最后,要显示计数器,我们需要做的是:

<counter.View />

Voilà! We built a single-page application with two unified layers and an encapsulated UI.

瞧! 我们构建了一个具有两个统一层和一个封装UI的单页应用程序。

概念证明 (Proof of Concept)

To experiment with the unified architecture, I built a RealWorld example app with Liaison.

为了试验统一体系结构,我使用Liaison构建了一个RealWorld示例应用程序

I might be biased, but the outcome looks pretty amazing to me: simple implementation, high code cohesion, 100% DRY, and no glue code.

我可能有偏见,但是结果对我来说看起来非常惊人:简单的实现,高代码内聚性,100% DRY和没有胶合代码。

In terms of the amount of code, my implementation is significantly lighter than any other one I have examined. Check out the results here.

就代码量而言,我的实现比我检查过的任何其他代码都要轻得多。 在这里查看结果

Certainly, the RealWorld example is a small application, but since it covers the most important concepts that are common to all applications, I'm confident that a unified architecture can scale up to more ambitious applications.

当然,RealWorld示例是一个小型应用程序,但是由于它涵盖了所有应用程序共有的最重要的概念,因此我相信统一体系结构可以扩展到更具野心的应用程序。

结论 (Conclusion)

Separation of concerns, loose coupling, simplicity, cohesion, and agility.

关注点分离,松散耦合,简单性,内聚性和敏捷性。

It seems we get it all, finally.

看来我们终于明白了。

If you are an experienced developer, I guess you feel a bit skeptical at this point, and this is totally fine. It is hard to leave behind years of established practices.

如果您是一位经验丰富的开发人员,那么我想您对此会有所怀疑,这完全可以。 很难留下多年的惯例。

If object-oriented programming is not your cup of tea, you will not want to use Liaison, and this is totally fine as well.

如果不是您不能喝面向对象的程序,那您就不想使用Liaison,这也很好。

But if you are into OOP, please keep a little window open in your mind, and the next time you have to build a full-stack application, try to see how it would fit in a unified architecture.

但是,如果您对OOP感兴趣,请在脑海中打开一个小窗口,下一次您必须构建全栈应用程序时,请尝试查看它在统一体系结构中的适用性。

Liaison is still at an early stage, but I am actively working on it, and I expect to release the first beta version in early 2020.

联络仍处于初期阶段,但我正在积极研究中,我希望在2020年初发布第一个Beta版本。

If you are interested, please star the repository and stay updated by following the blog or subscribing to the newsletter.

如果您有兴趣,请为存储库加注星标,并通过关注博客或订阅新闻通讯来保持更新。

Discuss this article on Changelog News.

Changelog News上讨论此文章

翻译自: https://www.freecodecamp.org/news/full-stack-unified-architecture/

web全栈架构师所需技术栈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值