技術情報ブログ
Angular
2021.11.10

Angularでマルチテナント対応(1)

 

はじめに

こんにちは。アーティサン株式会社の木戸です。

前編、後編と 2 回に渡って、Angular でのマルチテナント対応についてご紹介致します。
前編では、URL からテナントを判別し、対応したテナント情報を取得するまでを記載致します。

Angular でサブドメインを用いたマルチテナント対応を行いたい方に向けた記事となります。

 

マルチテナントとは

マルチテナントとは、同じシステムやサービスを、複数のユーザーが共有して利用する方式です。
SaaS などで用いられており、アカウント名や URL などからユーザーを区別し、それぞれの設定を用いて同じサービスを利用します。

 

環境

  • Node: 14.17.0

  • npm: 6.14.13

  • Angular: 12.1.1

  • TypeScript: 4.3.5

 

マルチテナント対応

フロントエンド側の Angular を用いてマルチテナントに対応するためには、接続した URL 等からテナントを判別し、各テナントに応じた設定情報を取得する必要があります。

今回の例ではサブドメインをテナント ID として利用し、各テナントの情報を取得します。
(テナント情報を取得する API は、別途バックエンドで用意されているものとします。)

例: https://tenant01.example.comの場合、テナント ID は tenant01 となります。

 

実装手順

 

マルチテナント機能用のモジュールを作成

マルチテナントを個別の機能として扱うため、モジュールを作成しメインのアプリケーションから分離します。

npx ng generate module features/tenant

 

テナント情報を表す型を作成

各テナント毎の設定情報を格納する型を定義します。
今回の例ではテーマカラーの情報を格納します。

// tenat-config.interface.ts
export interface TenantConfig {
  readonly themeColors: {
    readonly primary: string;
    readonly accent: string;
    readonly warn: string;
  };
}

 

URL からテナント ID を取得するサービスを作成

location オブジェクトから現在接続している URL を読み取り、テナント ID を取得するサービスを作成します。

テナント ID を取得する、getTenant メソッドを定義します。(10 行目)

// tenant.service.ts
import { DOCUMENT } from "@angular/common";
import { Inject, Injectable } from "@angular/core";

@Injectable()
export class TenantService {
  constructor(@Inject(DOCUMENT) private readonly document: Document) {}

  public getTenant(): string {
    return this.getTenantForHostname(this.document.location.hostname);
  }

  private getTenantForHostname(hostname: string): string {
    return hostname.split(".")[0].toLowerCase();
  }
}

 

テナント情報取得 API の URL を保持するクラスを作成

テンプレート文字列の形で、テナント情報取得 API の URL を保持するクラスを作成します。

テンプレートの書式をテナント ID に置き換えた URL を取得する、url プロパティを定義します。(10 行目)

今回の例では{tenantId}と書かれた文字列をテナント ID に入れ替えるシンプルな形としています。

例: テンプレート文字列をhttps://sample.example.com/{tenantId} 、テナント ID を testTenant とした場合、https://sample.example.com/testTenantとなります。

// config-url.ts
import { TenantService } from "tenant.service";

export class ConfigUrl {
  constructor(
    private readonly templateUrl: string,
    private readonly tenantService: TenantService
  ) {}

  public get url(): string {
    return this.templateUrl.replace(
      "{tenantId}",
      this.tenantService.getTenant()
    );
  }
}

 

API の URL とテナント ID からテナント情報を取得するサービスを作成

API の URL とテナント ID からテナント情報(テーマカラー等)を取得する、load メソッドを定義します。(18 行目)

// tenant-config.service.ts
import { DOCUMENT } from "@angular/common";
import { HttpClient } from "@angular/common/http";
import { Injectable, Inject } from "@angular/core";
import { TenantConfig } from "tenant-config.interface";
import { ConfigUrl } from "config-url";

@Injectable()
export class TenantConfigService {
  public config: TenantConfig | null = null;

  constructor(
    private readonly httpClient: HttpClient,
    @Inject(DOCUMENT) private readonly document: Document,
    private readonly configUrl: ConfigUrl
  ) {}

  public async load(): Promise<void> {
    try {
      this.config = await this.httpClient
        .get<TenantConfig>(this.configUrl.url)
        .toPromise();
    } catch {}
  }
}

 

Angular アプリケーション起動時にテナント情報を取得するようにモジュールを設定

APP_INITIALIZERを用いて、Angular アプリケーション起動時にテナント情報を取得します。(17 行目)

forRoot関数を定義し、マルチテナントモジュールインポート時に API の URL を外部から設定できる形にします。(25 行目)

 

APP_INITIALIZER とは

設定した関数がアプリケーション初期化中に実行されます。

これらの関数がPromiseまたはObservableを返す場合、Promise が解決されるか、Observable が完了するまで初期化は完了しません。

そのため、APP_INITIALIZERで Promise を返す関数(テナント情報取得関数)を設定することにより、 初期化完了時に、テナント情報が利用できるようになります。

// tenant.module.ts
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from "@angular/core";
import { TenantConfigService } from "tenant-config.service";
import { ConfigUrl } from "config-url";
import { TenantService } from "tenant.service";

export const initConfig =
  (tenantConfigService: TenantConfigService): (() => Promise<void>) =>
  () =>
    tenantConfigService.load();

@NgModule({
  providers: [
    TenantService,
    TenantConfigService,
    {
      provide: APP_INITIALIZER,
      useFactory: initConfig,
      deps: [TenantConfigService],
      multi: true,
    },
  ],
})
export class TenantModule {
  static forRoot(templateUrl: string): ModuleWithProviders<TenantModule> {
    return {
      ngModule: TenantModule,
      providers: [
        {
          provide: ConfigUrl,
          useFactory: (tenantService: TenantService): ConfigUrl =>
            new ConfigUrl(templateUrl, tenantService),
          deps: [TenantService],
        },
      ],
    };
  }
}

 

マルチテナントモジュールを Angular アプリケーション本体のモジュールにインポートする

マルチテナントモジュールインポート時に、forRoot メソッドを用いてテナント情報取得 API の URL を設定します。(14 行目)

APP_INITIALIZER により初期化時にテナント情報取得が完了しているため、アプリケーション起動後はTenantConfigService.configよりテナント情報を取得できます。

// app.module.ts
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { TenantModule } from "./features/tenant/tenant.module";
import { AppComponent } from "./app.component";

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    TenantModule.forRoot("https://sample.example.com/{tenantId}"),
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

 

あとがき

後編では、本記事で取得したテナント情報をもとに、tailwindcss でのテーマカラー変更、HTTP ヘッダへのテナント ID の付与をご紹介致します。

 

関連リンク

にTypeScriptでのフロントエンド開発を担当:木戸裕貴

木戸裕貴

私は主にTypeScriptでのフロントエンド開発を担当しております。

シェアする
記事カテゴリ
最新記事
Power Apps
2021.12.08

Power AppsアプリをPCとスマホで操作したい時の対応方針

Power Apps
2021.12.01

Dataverse for Teams・Power Apps:ドロップダウンメニューの順番を並び替えたい

Power Apps
2021.11.24

Automateのベストプラクティス・アンチパターン(1)【アクション名は変更すべき?Power】

Angular
2021.11.17

Angularでマルチテナント対応(2)

Angular
2021.11.10

Angularでマルチテナント対応(1)

人気記事ランキング
1
Power PlatformPower Apps
2020.10.02

世界に広がる「モデル駆動型アプリ」のココがスゴイ!【第1回】

2
Power PlatformPower AutomateSharePointExcel
2021.07.14

Power AutomateでExcelデータをSharePointにインポートするために考えること(第2回)

3
Power PlatformPower AutomateSharePointExcel
2021.06.23

Power AutomateでExcelデータをSharePointにインポートするために考えること(第1回)

4
Power PlatformPower AppsSharePoint
2021.07.16

Power AppsとSharePoint連携:SortByColumns関数で日本語列をソートする際の注意点

5
Power PlatformPower AutomateSharePointExcel
2021.08.11

Power AutomateでExcelデータをSharePointにインポートするために考えること(第5回)

PageTop
ページトップに戻る