アーティサン株式会社
  • Angular

Angularマルチテナント対応とは?サブドメインからテナント情報を取得する実装手順(1/2)

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

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

木戸裕貴

🖊木戸裕貴さんのブログ一覧はこちら

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

はじめに

Angularのマルチテナント対応とは、1つのアプリケーションで複数のテナント(顧客・組織)を識別し、それぞれに応じた設定を動的に切り替える仕組みです。本記事(前編)では、サブドメインをテナントIDとして利用し、Angular 12 / TypeScript 4.3 環境でAPP_INITIALIZERを活用してアプリ起動時にテナント情報を取得するモジュール構成と各サービスの実装手順を、TypeScriptコード付きで解説します。

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

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

Angularを使ったカスタム開発や、MicrosoftクラウドをベースとしたWebシステムの設計・開発について、お気軽にご相談ください。

 

マルチテナントとは

マルチテナントとは、同じシステムやサービスを、複数のユーザーが共有して利用する方式です。
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 {}

あとがき

本記事では、Angularでサブドメインを利用したマルチテナント対応を実装するための前編として、以下の7ステップを解説しました。

  1. マルチテナント機能用モジュールの作成(features/tenant
  2. テナント設定を格納する TenantConfig インターフェースの定義
  3. location オブジェクトからテナント ID を取得する TenantService の実装
  4. テンプレート文字列で API URL を管理する ConfigUrl クラスの作成
  5. テナント情報(テーマカラー等)を取得する TenantConfigService の実装
  6. APP_INITIALIZER を使ったアプリ起動時の非同期テナント情報取得
  7. TenantModule.forRoot() による API URL の外部注入設定

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

本記事のようなAngularによるフロントエンド実装や、Power PlatformをはじめとするMicrosoftクラウドの技術活用について、導入・開発支援を承っております。技術的なお悩みがあればお気軽にご連絡ください。