Ini adalah artikel pertama tentang topik ini, total 3 direncanakan:
- * Buat aplikasi root dari proyek Anda yang ada, tambahkan 3 aplikasi mikro ke dalamnya (vue, react, angular)
- Komunikasi antar aplikasi mikro
- Bekerja dengan git (terapkan, perbarui)
Daftar Isi
- bagian yang umum
- Mengapa itu dibutuhkan
- Buat wadah root (lihat definisi di bawah) dari monolit Anda
- Buat aplikasi mikro VUE (vue-app)
- Buat REACT aplikasi mikro (react-app)
- Buat aplikasi mikro ANGULAR (angular-app)
1. Bagian umum
Tujuan dari artikel ini adalah untuk menambahkan kemampuan untuk menggunakan proyek monolitik yang ada sebagai wadah root untuk arsitektur layanan mikro.
Proyek yang ada dibuat pada sudut 9.
Untuk arsitektur layanan mikro, kami menggunakan perpustakaan spa tunggal .
Anda perlu menambahkan 3 proyek ke proyek root, kami menggunakan teknologi yang berbeda: vue-app, angular-app, react-app (lihat halaman 4, 5, 6).
Sejalan dengan pembuatan artikel ini, saya mencoba menerapkan arsitektur ini ke dalam proyek produksi yang sedang saya kerjakan. Oleh karena itu, saya akan mencoba menjelaskan semua kesalahan yang saya miliki dalam proses pengembangan dan solusinya.
Aplikasi root (selanjutnya root) - root (wadah) aplikasi kita. Kami akan memasukkan (mendaftarkan) semua layanan mikro kami ke dalamnya. Jika Anda sudah memiliki proyek apa pun dan ingin menerapkan arsitektur ini di dalamnya, maka proyek Anda yang ada akan menjadi aplikasi root, di mana seiring waktu Anda akan mencoba untuk menggerogoti sebagian aplikasi Anda, membuat layanan mikro terpisah, dan mendaftarkannya di penampung ini.
Pendekatan pembuatan wadah akar ini akan memberikan peluang bagus untuk bermigrasi ke teknologi lain tanpa banyak kesulitan.
Misalnya, kami memutuskan untuk beralih dari sudut ke vue sepenuhnya, tetapi proyeknya berani, dan saat ini menghasilkan banyak uang untuk bisnis.
Tanpa arsitektur layanan mikro, ini tidak akan muncul di benak, hanya untuk orang-orang putus asa yang percaya pada unicorn dan bahwa kita semua adalah hologram.
Untuk beralih ke teknologi baru, pada kenyataannya perlu untuk menulis ulang seluruh proyek, dan hanya dengan begitu kita bisa menjadi tinggi dari kemunculannya dalam pertempuran.
Pilihan lainnya adalah arsitektur layanan mikro. Anda dapat membuat proyek root dari monolit Anda, menambahkan proyek baru di sana pada vue yang sama, mengatur roaming di root, selesai. Anda dapat bertempur, secara bertahap memotong potongan-potongan kecil dari akar proyek dan mentransfernya ke proyek mikro vue Anda. Ini hanya menyisakan file di penampung root Anda yang diperlukan untuk mengimpor proyek baru Anda.
Ini bisa dilakukan di sini dan sekarang, tanpa kehilangan, darah dan, yang terpenting, nyata.
Saya akan menggunakan angular sebagai root, karena proyek yang ada telah ditulis di dalamnya.
Antarmuka umum tempat aplikasi satu halaman akan dibungkus:
bootstrap (mounter, bus) - dipanggil setelah memuat layanan, akan memberi tahu elemen rumah mana yang perlu Anda pasang, berikan bus pesan ke mana layanan mikro akan berlangganan dan akan dapat mendengarkan dan mengirim permintaan dan perintah
mount () - pasang aplikasi dari rumah
unmount () - bongkar aplikasi
unload () - bongkar aplikasi
Dalam kode, saya akan sekali lagi menjelaskan pengoperasian setiap metode secara lokal di tempat penggunaan.
2. Mengapa itu dibutuhkan
Mari kita mulai dari poin ini secara ketat.
Ada 2 jenis arsitektur:
- Monolit
- Arsitektur layanan mikro
Dengan monolit, semuanya menjadi sangat sederhana dan akrab bagi kami. Kohesi yang kuat, blok kode yang sangat besar, repositori bersama, banyak metode.
Pada awalnya, arsitektur monolitik senyaman dan secepat mungkin. Tidak ada masalah dan kesulitan dalam membuat file integrasi, interlayer, model acara, bus data, dll.
Masalahnya muncul ketika proyek Anda berkembang, banyak fungsi yang terpisah dan kompleks untuk tujuan yang berbeda muncul. Semua fungsi ini mulai diikat dalam proyek ke beberapa jenis model umum, status, utilitas, antarmuka, metode, dll.
Juga, jumlah direktori dan file dalam proyek menjadi besar dari waktu ke waktu, ada masalah dalam menemukan dan memahami proyek secara keseluruhan, "tampilan atas" hilang, yang memberikan kejelasan tentang apa yang kita lakukan, di mana letak dan siapa yang membutuhkannya.
Selain semua ini, Hukum Eagleson sedang bekerja , yang mengatakan bahwa kode Anda, yang belum Anda lihat selama 6 bulan atau lebih, tampak seperti orang lain yang menulisnya.
Hal yang paling menyakitkan adalah bahwa semuanya akan tumbuh secara eksponensial, sebagai akibatnya, kruk akan dimulai, yang harus ditambahkan karena kompleksitas pemeliharaan kode sehubungan dengan hal di atas dan, seiring waktu, gelombang istilah yang tidak bertanggung jawab terjadi.
Akibatnya, jika Anda memiliki proyek langsung yang terus berkembang, itu akan menjadi masalah besar, ketidakpuasan abadi tim Anda, sejumlah besar orang - berjam-jam untuk membuat perubahan kecil pada proyek, ambang pintu masuk yang rendah untuk karyawan baru dan banyak waktu untuk meluncurkan proyek ke dalam pertempuran. Ini semua mengarah pada kekacauan, yah, kita suka ketertiban?
Apakah ini selalu terjadi dengan monolit?
Tentu saja tidak! Itu semua tergantung pada jenis proyek Anda, pada masalah yang muncul selama pengembangan tim. Proyek Anda mungkin tidak terlalu besar, untuk melakukan satu tugas bisnis yang rumit, ini normal dan saya yakin itu benar.
Pertama-tama, kita perlu memperhatikan parameter proyek kita.
Saya akan mencoba mengeluarkan poin-poin yang dengannya Anda dapat memahami apakah kita benar-benar membutuhkan arsitektur layanan mikro:
- 2 atau lebih tim sedang mengerjakan proyek, jumlah pengembang front-end adalah 10+;
- Proyek Anda terdiri dari 2 atau lebih model bisnis, misalnya, Anda memiliki toko online dengan jumlah barang yang banyak, filter, notifikasi, dan fungsi distribusi pengiriman kurir (2 model bisnis terpisah, bukan bisnis kecil yang akan saling mengganggu). Semua ini bisa hidup terpisah dan tidak bergantung satu sama lain.
- Kumpulan kemampuan UI tumbuh setiap hari atau setiap minggu tanpa memengaruhi bagian sistem lainnya.
Microfront digunakan untuk:
- Bagian-bagian terpisah dari frontend dapat dikembangkan, diuji, dan digunakan secara independen;
- Bagian ujung depan dapat ditambahkan, dilepas atau diganti tanpa perakitan ulang;
- .
- , - «», - ( ) -.
- ,
- .
single-spa ?
- (, React, Vue Angular) , .
- Single-spa , , .
- .
Microservice, menurut pemahaman saya, adalah aplikasi satu halaman independen yang hanya akan menyelesaikan satu tugas pengguna. Aplikasi ini juga tidak harus menyelesaikan seluruh tugas tim.
SystemJS adalah pustaka JS sumber terbuka yang biasa digunakan sebagai polyfill untuk browser.
Polyfill adalah bagian dari kode JS yang digunakan untuk menyediakan fungsionalitas modern untuk browser lama yang tidak mendukungnya.
Salah satu fitur SystemJS adalah peta impor, yang memungkinkan Anda mengimpor modul melalui jaringan dan memetakannya ke nama variabel.
Misalnya, Anda dapat menggunakan peta impor untuk pustaka React yang dimuat melalui CDN:
TAPI!
Jika Anda membuat proyek dari awal, bahkan dengan mempertimbangkan bahwa Anda telah menentukan semua parameter proyek Anda, Anda telah memutuskan bahwa Anda akan memiliki proyek mega super besar dengan tim yang terdiri dari 30+ orang, tunggu sebentar!
Saya sangat menyukai gagasan dari pendiri gagasan layanan mikro yang terkenal jahat - Martin Fowler .
Dia mengusulkan untuk menggabungkan pendekatan monolitik dan layanan mikro menjadi satu (MonolithFirst). Ide utamanya adalah sebagai berikut:
Anda tidak boleh memulai proyek baru dengan layanan mikro, meskipun Anda yakin sepenuhnya bahwa aplikasi di masa mendatang akan cukup besar untuk membenarkan pendekatan ini
Saya juga akan menjelaskan kerugian menggunakan arsitektur seperti itu di sini:
- Interaksi antar fragmen tidak dapat dicapai dengan metode tabung standar (DI, misalnya).
- Bagaimana dengan ketergantungan umum? Bagaimanapun, ukuran aplikasi akan tumbuh pesat jika tidak dikeluarkan dari fragmen.
- Seseorang harus tetap bertanggung jawab atas perutean di aplikasi akhir.
- Tidak jelas apa yang harus dilakukan dengan fakta bahwa layanan mikro yang berbeda dapat ditempatkan di domain yang berbeda
- Apa yang harus dilakukan jika salah satu fragmen tidak tersedia / tidak dapat dirender.
3. Membuat wadah root
Jadi, cukup teori, inilah saatnya untuk memulai.
Pergi ke konsol
ng add single-spa-angular
npm i systemjs@6.1.4,
npm i -d @types/systemjs@6.1.0,
npm import-map-overrides@1.8.0
Di ts.config.app.json, deklarasi impor global (tipe)
// ts.config.app.json
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": [
(+) "systemjs"
]
},
Tambahkan ke app-routing.module.ts semua aplikasi mikro yang kami tambahkan ke root
// app-routing.module.ts
{
path: 'vue-app',
children: [
{
path: '**',
loadChildren: ( ) => import('./spa-host/spa-host.module').then(m => m.SpaHostModule),
data: { app: '@somename/vue-app' }
}
]
},
{
path: 'angular-app',
children: [
{
path: '**',
loadChildren: ( ) => import('./spa-host/spa-host.module').then(m => m.SpaHostModule),
data: { app: '@somename/angular-app' }
}
]
},
{
path: 'react-app',
children: [
{
path: '**',
loadChildren: ( ) => import('./spa-host/spa-host.module').then(m => m.SpaHostModule),
data: { app: '@somename/react-app' }
}
]
},
Anda juga perlu menambahkan config
// extra-webpack.config.json
module.exports = (angularWebpackConfig, options) => {
return {
...angularWebpackConfig,
module: {
...angularWebpackConfig.module,
rules: [
...angularWebpackConfig.module.rules,
{
parser: {
system: false
}
}
]
}
};
}
Mari kita ubah file package.json, tambahkan semua yang diperlukan untuk pekerjaan itu
// package.json
"dependencies": {
...,
(+) "single-spa": "^5.4.2",
(+) "single-spa-angular": "^4.2.0",
(+) "import-map-overrides": "^1.8.0",
(+) "systemjs": "^6.1.4",
}
"devDependencies": {
...,
(+) "@angular-builders/custom-webpack": "^9",
(+) "@types/systemjs": "^6.1.0",
}
Tambahkan pustaka yang diperlukan ke angular.json
// angular.json
{
...,
"architect": {
"build": {
...,
"scripts": [
...,
(+) "node_modules/systemjs/dist/system.min.js",
(+) "node_modules/systemjs/dist/extras/amd.min.js",
(+) "node_modules/systemjs/dist/extras/named-exports.min.js",
(+) "node_modules/systemjs/dist/extras/named-register.min.js",
(+) "node_modules/import-map-overrides/dist/import-map-overrides.js"
]
}
}
},
Buat folder spa tunggal di root proyek . Mari tambahkan 2 file ke dalamnya.
1. route-reuse-strategy.ts adalah file perutean layanan mikro kami.
Jika aplikasi anak merutekan secara internal, aplikasi itu menafsirkannya sebagai perubahan rute.
Secara default, ini akan menghancurkan komponen saat ini dan menggantinya dengan instance baru dari komponen spa-host yang sama.
Strategi penggunaan ulang rute ini melihat routeData.app untuk menentukan apakah rute baru harus diperlakukan sebagai rute yang sama seperti rute sebelumnya, memastikan bahwa kita tidak memasang ulang aplikasi anak saat aplikasi anak yang ditentukan dirutekan secara internal.
// route-reuse-strategy.ts
import { RouteReuseStrategy, ActivatedRouteSnapshot, DetachedRouteHandle } from '@angular/router';
import { Injectable } from '@angular/core';
@Injectable()
export class MicroFrontendRouteReuseStrategy extends RouteReuseStrategy {
shouldDetach(): boolean {
//
return false;
}
store(): void { }
shouldAttach(): boolean {
return false;
}
//
retrieve(): DetachedRouteHandle {
return null;
}
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
return future.routeConfig === curr.routeConfig || (future.data.app && (future.data.app === curr.data.app));
}
}
2. Layanan single-spa.service.ts
Layanan ini akan menyimpan metode untuk memasang (memasang) dan melepas (melepas) aplikasi mikro-frontend.
mount adalah fungsi siklus hidup yang akan dipanggil setiap kali aplikasi terdaftar tidak dipasang, tetapi fungsi aktivitasnya mengembalikan nilai true. Saat dipanggil, fungsi ini harus melihat URL untuk menentukan rute aktif dan kemudian membuat elemen DOM, peristiwa DOM, dll.
unmount adalah fungsi siklus hidup yang akan dipanggil setiap kali aplikasi terdaftar dipasang, tetapi fungsi aktivitasnya mengembalikan nilai false. Saat dipanggil, fungsi ini harus menghapus semua elemen DOM.
//single-spa.service.ts
import { Injectable } from '@angular/core';
import { mountRootParcel, Parcel, ParcelConfig } from 'single-spa';
import { Observable, from, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class SingleSpaService {
private loadedParcels: {
[appName: string]: Parcel;
} = {};
mount(appName: string, domElement: HTMLElement): Observable<unknown> {
return from(System.import<ParcelConfig>(appName)).pipe(
tap((app: ParcelConfig) => {
this.loadedParcels[appName] = mountRootParcel(app, {
domElement
});
})
);
}
unmount(appName: string): Observable<unknown> {
return from(this.loadedParcels[appName].unmount()).pipe(
tap(( ) => delete this.loadedParcels[appName])
);
}
}
Selanjutnya, kami membuat wadah direktori / app / spa-host .
Modul ini akan mengimplementasikan registrasi dan pemetaan aplikasi micro frontend kami ke root.
Mari tambahkan 3 file ke modul.
1. Modul itu sendiri spa-host.module.ts
//spa-host.module.ts
import { RouterModule, Routes } from '@angular/router';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { SpaUnmountGuard } from './spa-unmount.guard';
import { SpaHostComponent } from './spa-host.component';
const routes: Routes = [
{
path: '',
canDeactivate: [SpaUnmountGuard],
component: SpaHostComponent,
},
];
@NgModule({
declarations: [SpaHostComponent],
imports: [CommonModule, RouterModule.forChild(routes)]
})
export class SpaHostModule {}
2. Komponen spa-host.component.ts - mengoordinasikan instalasi dan pembongkaran aplikasi mikro-frontend
// spa-host.component.ts
import { Component, OnInit, ViewChild, ElementRef, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import {SingleSpaService} from '../../single-spa/single-spa.service';
@Component({
selector: 'app-spa-host',
template: '<div #appContainer></div>',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SpaHostComponent implements OnInit {
@ViewChild('appContainer', { static: true })
appContainerRef: ElementRef;
appName: string;
constructor(private singleSpaService: SingleSpaService, private route: ActivatedRoute) { }
ngOnInit() {
//
this.appName = this.route.snapshot.data.app;
this.mount().subscribe();
}
//
mount(): Observable<unknown> {
return this.singleSpaService.mount(this.appName, this.appContainerRef.nativeElement);
}
//
unmount(): Observable<unknown> {
return this.singleSpaService.unmount(this.appName);
}
}
3. spa-unmount.guard.ts - memeriksa apakah nama aplikasi di rute berbeda, parsing layanan sebelumnya, jika terlalu, buka saja.
// spa-unmount.guard.ts
import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { SpaHostComponent } from './spa-host.component';
@Injectable({ providedIn: 'root' })
export class SpaUnmountGuard implements CanDeactivate<SpaHostComponent> {
canDeactivate(
component: SpaHostComponent,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState: RouterStateSnapshot
): boolean | Observable<boolean> {
const currentApp = component.appName;
const nextApp = this.extractAppDataFromRouteTree(nextState.root);
if (currentApp === nextApp) {
return true;
}
return component.unmount().pipe(map(_ => true));
}
private extractAppDataFromRouteTree(routeFragment: ActivatedRouteSnapshot): string {
if (routeFragment.data && routeFragment.data.app) {
return routeFragment.data.app;
}
if (!routeFragment.children.length) {
return null;
}
return routeFragment.children.map(r => this.extractAppDataFromRouteTree(r)).find(r => r !== null);
}
}
Kami mendaftarkan semua yang kami tambahkan ke app.module
// app.module.ts
providers: [
...,
{
(+) provide: RouteReuseStrategy,
(+) useClass: MicroFrontendRouteReuseStrategy
}
]
Mari kita ubah main.js.
// main.ts
import { enableProdMode, NgZone } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { start as singleSpaStart } from 'single-spa';
import { getSingleSpaExtraProviders } from 'single-spa-angular';
import { AppModule } from './app/app.module';
import { PlatformLocation } from '@angular/common';
if (environment.production) {
enableProdMode();
}
singleSpaStart();
//
const appId = 'container-app';
// , getSingleSpaExtraProviders.
platformBrowserDynamic(getSingleSpaExtraProviders()).bootstrapModule(AppModule).then(module => {
NgZone.isInAngularZone = () => {
// @ts-ignore
return window.Zone.current._properties[appId] === true;
};
const rootPlatformLocation = module.injector.get(PlatformLocation) as any;
const rootZone = module.injector.get(NgZone);
// tslint:disable-next-line:no-string-literal
rootZone['_inner']._properties[appId] = true;
rootPlatformLocation.setNgZone(rootZone);
})
.catch(err => {});
Selanjutnya, kami membuat file import-map.json di folder share. File tersebut diperlukan untuk menambahkan peta impor.
Saat ini, kami akan mengosongkannya dan mengisi saat aplikasi ditambahkan ke root.
<head>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My first microfrontend root project</title>
<base href="/">
...
(+) <meta name="importmap-type" content="systemjs-importmap" />
<script type="systemjs-importmap" src="/assets/import-map.json"></script>
</head>
<body>
<app-root></app-root>
<import-map-overrides-full></import-map-overrides-full>
<noscript>Please enable JavaScript to continue using this application.</noscript>
</body>
</html>
4. Buat aplikasi mikro VUE (vue-app)
Sekarang kami telah menambahkan kemampuan untuk menjadi aplikasi root untuk proyek monolitik kami, sekarang saatnya membuat aplikasi mikro eksternal pertama kami dengan spa tunggal.
Pertama, kita perlu menginstal create-single-spa secara global, antarmuka baris perintah yang akan membantu kita membuat proyek spa tunggal baru dengan perintah sederhana.
Pergi ke konsol
npm install --global create-single-spa
Buat aplikasi vue sederhana menggunakan perintah di konsol
create-single-spa
Antarmuka baris perintah akan meminta Anda untuk memilih direktori, nama proyek, organisasi dan jenis aplikasi yang akan dibuat
? Directory for new project vue-app
? Select type to generate single-spa application / parcel
? Which framework do you want to use? vue
? Which package manager do you want to use? npm
? Organization name (use lowercase and dashes) somename
Kami meluncurkan aplikasi mikro kami
npm i
npm run serve --port 8000
Saat kita memasukkan path di browser localhost : 8080 / , dalam kasus vue, kita akan melihat layar kosong. Apa yang terjadi?
Karena tidak ada file index.js di aplikasi mikro yang dihasilkan.
Single-spa menyediakan taman bermain untuk mengunduh aplikasi melalui internet, jadi mari kita gunakan dulu.
Tambahkan ke index.js
single-spa-playground.org/playground/instant-test?name=@some-name/vue-app&url=8000Saat membuat aplikasi root, kami menambahkan peta terlebih dahulu untuk memuat proyek vue kami.
{
"imports": {
... ,
"vue": "https://unpkg.com/vue",
"vue-router": "https://cdn.jsdelivr.net/npm/vue-router@3.0.7/dist/vue-router.min.js",
"@somename/vue-app": "//localhost:8080/js/app.js"
}
}
Siap! Sekarang dari proyek root sudut kami, kami dapat memuat aplikasi mikro yang ditulis dalam vue.
5. Buat REACT aplikasi mikro (react-app)
Kami membuat aplikasi reaksi sederhana yang serupa menggunakan perintah di konsol
create-single-spa
Nama organisasi: nama nama Nama
proyek: react-app
? Directory for new project react-app
? Select type to generate single-spa application / parcel
? Which framework do you want to use? react
? Which package manager do you want to use? npm
? Organization name (use lowercase and dashes) somename
Mari kita periksa apakah kita telah menambahkan peta impor di aplikasi root kita
{
"imports": {
... ,
"react": "https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.development.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.development.js",
"@somename/react-app": "//localhost:8081/somename-projname.js",
}
}
Selesai! Sekarang, di rute aplikasi react kami, kami memuat proyek mikro react.
6. Buat aplikasi mikro ANGULAR (angular-app)
Kami membuat aplikasi mikro Angular dengan cara yang persis sama seperti 2 sebelumnya
create-single-spa
Nama organisasi: nama nama Nama
proyek: angular-app
? Directory for new project angular-app
? Select type to generate single-spa application / parcel
? Which framework do you want to use? angular
? Which package manager do you want to use? npm
? Organization name (use lowercase and dashes) somename
Mari kita periksa apakah kita telah menambahkan peta impor di aplikasi root kita
{
"imports": {
... ,
"@somename/angular-app": "//localhost:8082/main.js",
}
}
Kami meluncurkan, memeriksa, semuanya harus berfungsi.
Ini adalah posting pertama saya di HabrΓ©, saya akan sangat berterima kasih atas komentar Anda.