咱们生活在一个微服务的国际里,而且这个国际将持续存在。后端开发者需求深入研究范畴驱动规划,编写无状况、有弹性、高可用的服务,经过变更数据捕获坚持数据同步,处理网络故障,处理认证、授权、JWT……并公开一个漂亮的超媒体API,以便前端开发者可认为其增加一个巨大的用户界面。
很好!但是前端怎么办?
假定咱们有几个微服务,几个团队,几个API,因而也有几个用户界面。你怎样才能创立一个集成的、共同的用户界面,使你的用户实际上看不到有多少微服务就有多少用户界面?在这篇文章中,我将运用Angular库完成客户端UI组合规划形式。
一个微服务架构
假定咱们有一个CD BookStore应用程序,答应客户在线购买CD和书本,也答应管理员查看库存。咱们也有一些杂乱的ISBN号码生成器需求处理。假如咱们将这个应用程序建模为微服务,咱们终究或许会将其分割成3个独立的微服务:
- 商铺:处理显现CD和书本的信息,答应用户购买。
- 库存:答应管理员查看库房中物品的可用性,假如需求,可以购买新的物品。
- 生成器:在每次创立新书时生成ISBN号码。
咱们终究有两个人物(用户和管理员)经过一个用户界面与这3个API进行交互:
客户端UI构成
在一个完美的微服务国际里,这3个微服务是独立的,由3个不同的团队开发,因而,有3个不同的用户界面,每个都以自己的速度发布。一方面,咱们有3个不同的UI,另一方面,咱们希望咱们的用户可以浏览到他们所看到的一个单一的、集成的应用程序。有几种方法可以做到这一点,而我在这里描绘的一种方法被称为客户端UI组合规划形式。
问题
怎么完成一个显现多个服务数据的UI屏幕或页面?解决方案
每个团队开发一个客户端UI组件,比方Angular组件,完成他们服务的页面/屏幕的区域。一个UI团队负责完成页面骨架,经过组合多个特定服务的UI组件来构建页面/屏幕。
咱们的想法是,在客户端将咱们的3个用户界面聚组成一个单一的界面,并终究得到类似这样的东西。
假如运用兼容的技术,将几个用户界面聚组成一个单一的界面作用会更好。在这个例子中,我运用Angular,而且只运用Angular来创立三个用户界面和页面骨架。当然,Web Components也非常适合这个用例,但这并不是本文的意图。
Angular库
自Angular CLI 6以来,一个新奇的功用是可以轻松地创立库。回到咱们的架构,咱们可以说页面骨架是Angular应用程序(CDBookStore应用程序),然后,每个微服务的用户界面是一个库(商铺、库存、发电机)。
在Angular CLI指令方面,这便是咱们的情况:
\[sourcecode language="shell"\]
\# 主应用程序叫CDBookStore
$ ng new cdbookstore -directory cdbookstore -routing true
\# 咱们的3个微服务UI的3个库
$ ng generate library store -prefix str
$ ng generate library inventory -prefix inv
$ ng generate library generator -prefix gen
\[/sourcecode\]
一旦你执行了这些指令,你将得到以下目录结构:
-
- src/app/是杰出的老式Angular结构。在这个目录中,咱们将有CDBookStore应用程序的页面骨架。正如你将看到的,这个骨架页面只是一个侧边栏,用来从一个微服务用户界面导航到另一个:
- projects/是一切库的目录
- projects/generator: Generator微服务的用户界面
- projects/inventory:Inventory微服务的用户界面
- projects/store: Store微服务的用户界面
骨架页面
骨架页面是为了聚合一切其他的组件。基本上,它只要一个侧边栏菜单(答应咱们从一个微服务UI导航到另一个)和一个。它是你可以具有登录/刊出和其他用户偏好的地方。它看起来像这样:
在代码方面,虽然运用了Bootstrap,但没有什么特别之处。风趣的是界说路由的方法。AppRoutingModule只界说了主程序的路由(这里是主页和关于菜单)。
\[sourcecode language="javascript" title="app-routing.module.ts"\]
const routes:Routes = \[
{path:", component:HomeComponent},
{path: 'home', component:HomeComponent},
{path: 'about', component:AboutComponent},
\];
@NgModule({
imports:\[RouterModule.forRoot(routes)\],
exports:\[RouterModule\]
})
export class AppRoutingModule { }
\[/sourcecode\]
在侧边菜单栏的HTML一侧,咱们可以找到导航指令和路由器:
\[sourcecode language="html" title="app.component.html"\]
<ul class="list-unstyled components">
<li>
<a \[routerLink\]="\['/home'\]">
<i class="fas fa-home"></i>
主页
</a>
<a \[routerLink\]="\['/str'\]">
<i class="fas fa-store"></i>
商铺
</a>
<a \[routerLink\]="\['/inv'\]">
<i class="fas fa-chart-bar"></i>
存货
</a>
<a \[routerLink\]="\['/gen'\]">
<i class="fas fa-cogs"></i>
Generator
</a>
/li>
</ul>
<!- 页面内容 ->
<div id="content">
<router-outlet></router-outlet>
</div>
\[/sourcecode\]
正如你在上面的代码中看到的,routerLink指令被用来导航到首要的应用程序组件以及库组件。这里的诀窍是,/home被界说在路由模块中,但不是/str、/inv或/gen。这些路由是在库自身中界说的。
免责声明:我是一个糟糕的网页规划师/开发员。假如有人看到这篇文章,知道JQuery和Angular,并想帮我一把,我很想让侧边栏可以折叠起来。我甚至为你创立了一个问题;o)
库
现在,当咱们点击商铺、库存或发电机时,咱们希望路由器可以导航到库的组件:
留意路由器之间的父/子联系。在上面的图片中,红色的路由器出口是父级,归于主应用程序。绿色的router-outlet是子节点,归于库。留意,在store-routing.module.ts中,咱们运用str有主途径,一切其他的途径都是childs(运用children关键字)。
\[sourcecode language="javascript" title="store-routing.module.ts" highlight="3″\]
const routes:Routes = \[
{
path: 'str', component:StoreComponent, children:\[
{path:", component:HomeComponent}, {path: 'home', component:HomeComponent}, {path: 'book-list', component:BookListComponent}, {path: 'book-detail', component: BookDetailComponent}:BookDetailComponent}, \]
},
\] 。
@NgModule({
imports:\[RouterModule.forRoot(routes)\],
exports:\[RouterModule\]
})
export class StoreRoutingModule {
}
\[/sourcecode\]
有一个父/子路由器联系的好处是,你可以从 “功用 “导航中获益。这意味着,假如你导航到/home或/about,你会留在主程序中,但假如你导航到/str、/str/home、/str/book-list或/str/book-detail,你会导航到Store库。你甚至可以在每个功用的基础上进行懒散加载(只在需求时加载商铺库)。
Store库在顶部有一个导航条,因而它需求routerLink指令。留意,咱们只需求运用子途径[routerLink]=”[‘book-list’]”而不需求指定/str(不需求[routerLink]=”[‘/str/book-list’]”)。
\[sourcecode language="html" title="商铺。html"\]
<nav>
<div class="collapse navbar-collapse">
<ul class="navbar-nav mr-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle">Items</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
<a class="dropdown-item" \[routerLink\]="\['book-list'\]">书本</a>
<a class="dropdown-item" \[routerLink\]="\['cd-list'\]">CD</a>
<a class="dropdown-item" \[routerLink\]="\['dvd-list'\]">DVDs</a>
</div>
</li>
</ul>
</div>
</nav>
<!- 页面内容 ->
<div id="content">
<router-outlet></router-outlet>
</div>
\[/sourcecode\]
长处和缺点
客户端UI组合规划形式有一些长处和缺点。假如你已经有了每个微服务的用户界面,而且每个微服务都运用了完全不同的技术(Angular vs React vs Vue.JS vs …)或不同的框架版本(Bootstrap 2 vs Bootstrap 4),那么聚合它们或许会很麻烦,你或许不得不重写一些片段以使它们兼容。
但这实际上是有好处的。假如你没有成百上千的开发团队,你或许终究会成为从一个团队转移到另一个团队的人,并且会很高兴发现(或多或少)相同的技术。这在底盘形式中被界说。关于你的终究用户来说,应用程序将具有相同的外观和感觉(在手机、笔记本电脑、桌面、平板电脑上),这给人一种整合的感觉。
以Angular库的结构方法,具有一个单一的repo要容易得多。当然,每个库都可以有自己的生命周期,并且是独立的,但在一天结束时,把它们放在同一个 repo 上会更容易。我不知道你是否喜欢MonoRepos,但有些人喜欢;o)
结语
咱们架构和开发微服务已经有几十年了(是的,它曾经被称为分布式系统,或分布式服务),所以咱们大致知道怎么让它们在后端工作。现在,咱们面对的挑战是,怎么让一个用户界面与不同团队开发的多个微服务进行沟通,一起让终端用户感到共同和集成。你或许并不总是有这种需求(看看亚马逊是怎么为其服务提供完全不同的用户界面的),但假如你有这种需求,那么客户端UI组合规划形式便是一个很好的挑选。
假如你想尝试一下,请下载代码,安装并运行它(只要前端,没有后端API)。不要忘记给我一些反应。我很想知道你是怎么将几个用户界面聚组成 “一个 “的。