阿里云「飞天发布时刻」2024来啦!新产品、新特性、新能力、新方案,等你来探~ 了解详情
写点什么

如何使用统一架构简化全栈开发

  • 2020-01-12
  • 本文字数:4850 字

    阅读完需:约 16 分钟

如何使用统一架构简化全栈开发

现代的全栈应用程序通常由六层组成:数据访问、后端模型、API 服务端、API 客户端、前端模型和用户界面。我们需要大量的胶水代码才能将它们全部连接起来,并且领域模型在整个栈中存在重复。因此,开发的敏捷性受到了极大的影响。本文如何使用统一架构来构建全栈应用程序,以及统一架构语言扩展 Liaison。



现代的全栈应用程序(例如,单页应用程序或移动应用程序)通常由六层组成:


  • 数据访问

  • 后端模型

  • API 服务端

  • API 客户端

  • 前端模型

  • 用户界面


通过这种架构方式,我们可以实现某些设计良好的应用程序特性,例如关注点分离(separation of concerns,SoC)耦合(loose coupling)


但它也并非没有缺点。它通常是以牺牲其他一些重要特性为代价的,比如简单性、内聚性或敏捷性。


似乎我们不可能拥有上述全部特性。我们必须妥协。


但问题在于,通常每一层都是作为一个完全不同的世界被单独构建的。


即使这些层都是使用相同的语言实现的,它们之间也不能很容易地通信和共享。


我们需要大量的胶水代码才能将它们全部连接起来,并且领域模型重复地存在于整个栈中。因此,开发的敏捷性受到了极大的影响。


例如,向模型中添加一个简单的字段通常需要修改栈中的所有层。难道您不觉得这有点可笑吗?


最近我一直在思考这个问题,我相信我已经找到了解决的办法。


诀窍在于:当然,应用程序的层必须是“物理上”的分割,但不需要是“逻辑上”的分割。

统一架构


在面向对象编程中,当我们使用继承时,我们可以得到一些类,并从两种角度来观察它们:物理和逻辑。这是什么意思呢?


假设我们有一个继承自 A 类的 B 类,那么,可以将 A 和 B 看作是两个物理类。但是在逻辑上,它们并不是分离的,B 可以被看作是一个逻辑类,它是由 A 的属性和其自身的属性组成的。


例如,当我们在类中调用某个方法时,我们不必担心这个方法是在这个类中实现的还是在它的父类中实现的。从调用方的角度来看,只需要担心一个类即可。父类和子类被统一成一个逻辑类了。


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


这样做,前端和后端将被统一到一个单一的逻辑层,这将消除所有通信和共享问题。实际上,可以从前端直接访问后端的类、属性和方法。


当然,我们通常不希望将整个后端都暴露给前端。但是类继承也是如此,并且它有一个优雅的解决方案叫做“私有属性”。类似地,后端也可以有选择地暴露一些属性和方法。


能够从一个统一的世界中掌握应用程序的所有层并不是一件小事。它完全改变了游戏规则。这就像是从三维世界降到二维世界。一切都变得容易多了。


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


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


但在我们开始之前,让我们先对技术栈进行下分解,看看每层应该如何适用于统一架构。

数据访问

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


对于更复杂的应用程序,我们可能必须优化数据库模式和请求。但我们不想因为这些问题而使后端模型变得混乱,因此此处可能需要额外附加一层。


我们构建一个数据访问层来实现优化关注点,而这通常发生在开发周期的后期(如果真的会发生的话)。


不管怎样,如果我们需要这样一个层,我们可以稍后再构建它。通过跨层继承,我们可以在后端模型层上再添加一个数据访问层,而这几乎不需要对现有代码进行任何更改。

后端模型

通常,后端模型层具有如下职责:


  • 塑造领域模型。

  • 实现业务逻辑。

  • 处理授权机制。


对于大多数后端,最好在一个单一层中实现它的全部职责。但是,如果我们希望单独处理一些关注点,例如,如果我们希望将授权与业务逻辑分开,那么我们可以在两个相互继承的层中实现它们。

API 层

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


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


Web API 无非就是胶水代码,并且构建起来非常麻烦。所以,如果我们能避免,这将是一个巨大的进步。


幸运的是,我们可以再次利用跨层继承。在统一架构中,不需要构建 Web API。我们所要做的就是让前端模型从后端模型中继承,这样就完成了。


然而,仍然存在一些需要构建 Web API 的很好用例。这时,我们需要向某些第三方开发人员公开后端,或者需要与某些遗留的旧系统进行集成。


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


关于这个主题的更多信息可以在这篇文章中找到。

前端模型

因为后端是事实来源,所以它应该实现所有的业务逻辑,而前端不应该实现任何业务逻辑。因此,前端模型只是简单地继承自后端模型而已,几乎没有添加任何内容。

用户界面

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


当前端模型由类构成时,可以将视图封装为简单的方法。如果您现在不明白我的意思,请不用担心,在后面的示例中我会给出更清楚解释。


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


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

将一切组装起来

统一架构使我们能够将 6 个物理层统一为 1 个逻辑层:


  • 在最小的实现中,数据访问被封装到了后端模型中,UI 也被封装到了前端模型中。

  • 前端模型继承自后端模型。

  • 不再需要 API 层。


结果如下图所示:



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

Liaison

为了实现一个统一的架构,我们所需的只是跨层继承,而我是通过构建Liaison来实现这一点的。


如果您愿意的话,可以把 Liaison 看作一个框架,但是我更喜欢把它描述成一个语言扩展,因为它的所有特性都位于尽可能低的级别上:编程语言级别。


所以,Liaison 并不会把您锁定在一个预定义的框架中,而是可以在其之上创建一个完整的宇宙。您可以在这篇文章中阅读到更多关于此主题的信息。


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


但是 Liaison 是完全不同的:


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

  • 它是在语言级别实现的(见上文)

  • 它的设计简单明了,并且公开了最少的 API。

  • 它不涉及任何样板代码、生成的代码、配置文件或工件。

  • 它使用了一个简单但功能强大的序列化协议(Deepr),该协议支持一些独特的特性,比如链式调用、自动化批处理或部分执行。


Liaison 始于 JavaScript,但是它所解决的问题是通用的,并且可以将它移植到任何面向对象的语言中而不会带来太多的麻烦。

Hello 计数器

让我们通过将经典的“计数器”示例实现成一个单页应用程序来说明 Liaison 是如何工作的吧。


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


// shared.js
import {Model, field} from '@liaison/liaison';
export class Counter extends Model { // 共享类定义一个字段来跟踪计数器的值 @field('number') value = 0;}
复制代码


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


// backend.js
import {Layer, expose} from '@liaison/liaison';
import {Counter as BaseCounter} from './shared';
class Counter extends BaseCounter { // 我们将“value”字段暴露给前端 @expose({get: true, set: true}) value;
// 我同样将increment() 方法暴露给前端 @expose({call: true}) increment() { this.value++; }}
// 我们将后端类注册到导出层中export const backendLayer = new Layer({Counter});
复制代码


最后,让我们来构建前端:


// frontend.js
import {Layer} from '@liaison/liaison';
import {Counter as BaseCounter} from './shared';import {backendLayer} from './backend';
class Counter extends BaseCounter { // 目前,前端类只是继承共享类}
// 我们将前端类注册到一个继承自后端层的层中const frontendLayer = new Layer({Counter}, {parent: backendLayer});
// 最后,我们实例化一个计数器const counter = new frontendLayer.Counter();
// 运行计数器await counter.increment();console.log(counter.value); // => 1
复制代码


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


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


从前端的角度来看,该操作是透明的。它不需要知道哪个方法被远程调用了。它只是调用。


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


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


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


如何跨前端和后端重写一个方法呢?这与常规的 JavaScript 没有什么不同,我们可以使用 super。例如,我们可以重写 increment()方法以在前端的上下文中运行额外的代码:


// frontend.js
class Counter extends BaseCounter { async increment() { await super.increment(); // 后端的`increment()` 方法被调用 console.log(this.value); // 在前端添加额外的运行代码 }}
复制代码


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


// frontend.js
import React from 'react';import {view} from '@liaison/react-integration';
class Counter extends BaseCounter { // 我们使用`@view()`装饰器来观察模型,并在需要时重新渲染视图 @view() View() { return ( <div> {this.value} <button onClick={() => this.increment()}>+</button> </div> ); }}
复制代码


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


<counter.View />
复制代码


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

概念验证

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


我可能有些自夸了,但结果看起来真的非常惊人:实现简单,代码高内聚,100% DRY(Don’t repeat yourself),没有胶水代码。


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


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

结论

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


似乎这一切都实现了。


如果您是一位经验丰富的开发人员,那么我想您对此会有所怀疑,这也很正常。我们很难把多年的习惯抛诸脑后。


如果您不喜欢面向对象编程,那么就不要使用 Liaison 了,这也是完全没有问题的。


但是,如果您对 OOP 感兴趣,请在脑海中打开一扇小窗,下一次您必须构建一个全栈应用程序时,请试试看它是如何适用于统一架构的。


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


如果您有兴趣的话,请为代码库加注 star 标,并通过关注博客或订阅newsletter的方式来保持更新。


原文链接:


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


公众号推荐:

跳进 AI 的奇妙世界,一起探索未来工作的新风貌!想要深入了解 AI 如何成为产业创新的新引擎?好奇哪些城市正成为 AI 人才的新磁场?《中国生成式 AI 开发者洞察 2024》由 InfoQ 研究中心精心打造,为你深度解锁生成式 AI 领域的最新开发者动态。无论你是资深研发者,还是对生成式 AI 充满好奇的新手,这份报告都是你不可错过的知识宝典。欢迎大家扫码关注「AI前线」公众号,回复「开发者洞察」领取。

2020-01-12 23:002742
用户头像
刘燕 InfoQ高级技术编辑

发布了 1112 篇内容, 共 493.4 次阅读, 收获喜欢 1966 次。

关注

评论

发布
暂无评论
发现更多内容

新起点!大数据分布式可视化的 DAG 任务调度系统 Taier 正式发布1.4版本

袋鼠云数栈

开源

如何看待马斯克3月呼吁暂停人工智能,4月却创立TruthGPT

这我可不懂

低代码 JNPF

算法题每日一练:组合总和 II

知心宝贝

数据结构 算法 前端 后端 三周年连更

数据中心厂商超云加入龙蜥社区,多款服务器完成与龙蜥操作系统适配

OpenAnolis小助手

开源 操作系统 龙蜥社区 CLA 长城超云

舒明:稳定支撑日高峰亿级保单交易,国泰产险的运维创新实践

OceanBase 数据库

数据库 oceanbase

ITMS-90433: Invalid Swift Support

雪奈椰子

软件测试/测试开发丨Pytest 自动化测试框架(二)

测试人

软件测试 自动化测试 测试开发 pytest

阿里云张献涛:云原生计算基础设施助力汽车行业数字化升级

云布道师

云计算

网络工程师经常搞混的路由策略和策略路由,两者到底有啥区别?

wljslmz

三周年连更

用户痛点与根因识别:解锁企业数字化转型成功的关键

L3C老司机

火山引擎边缘云,助力业务敏捷创新

火山引擎边缘云

CDN 边缘计算 火山引擎 边缘云

中国年轻人阅读洞察2023

易观分析

年轻人 阅读

云原生时代,不可不知的基础设施即代码(IaC)

极狐GitLab

DevOps 云原生 CI/CD gitops IaC

分析nginx访问日志,统计前10的ip

linux大本营

nginx 日志

MobTech 秒验|守护账户安全

MobTech袤博科技

PageObject设计模式

测吧(北京)科技有限公司

测试

性能报告 | YMatrix 5.0 对比 Greenplum 超 12 倍性能提升,TPC-H 基准测试报告发布

YMatrix 超融合数据库

时序数据库 测试工具 数据库、

FastAPI 快速开发 Web API 项目: 通过 SQLAlchemy 进行数据操作

宇宙之一粟

Python sqlalchemy FastApi 三周年连更

Java EE应用服务器的事务管理

swordholder

Java transaction JavaEE

怎么用systemd进行进程守护

linux大本营

Linux 进程 守护进程

OpenKruise V1.4 版本解读:新增 Job Sidecar Terminator 能力

阿里巴巴云原生

阿里云 开源 云原生 OpenKruise

什么是全民开发?|概念、技能和优势

草料二维码

低代码 无代码

C++实现消息队列

linux大本营

C++ 消息列队

WebIntents 翻身战,操作系统实现无缝集成的王牌

鼎道智联

AI

使用体验 I 早知道 TDesign 支持 AVIF 图片压缩,我就不用为流量和格式发愁啦!

TDesign

前端 图片压缩 图片格式

连续3天3场分享,KubeVela@KubeCon EU 2023 抢鲜看!

阿里巴巴云原生

阿里云 开源 云原生 KubeVela

火山引擎 DataLeap 下 Notebook 系列文章二:技术路线解析

字节跳动数据平台

数据治理 数据研发 企业号 4 月 PK 榜

火山引擎云原生数据仓库ByteHouse技术白皮书V1.0 (Ⅳ)

字节跳动数据平台

数据导入 实时数据 实时导入 企业号 4 月 PK 榜

如何填写苹果应用上架过程中的隐私政策信息?

雪奈椰子

OceanBase 4.1解读:我们想给用户一个开箱即用的OceanBase部署运维工具

OceanBase 数据库

数据库 oceanbase

从零学习SDK(8)SDK的集成和部署

MobTech袤博科技

如何使用统一架构简化全栈开发_AI&大模型_Manuel Vila_InfoQ精选文章