技術情報ブログ
Angular
2021.11.10

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

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でのフロントエンド開発を担当しております。

シェアする
記事カテゴリ
最新記事
2024.03.27

Power Automateのベストプラクティス・アンチパターン(5)【Apply to each×コンカレンシー×変数の設定はNG】

2024.03.20

Power Apps×Teams:Teams上からPower Appsを実行する方法

2024.03.13

Power Apps:SharePointリストと連携したカレンダーアプリを自作してみよう

2024.03.06

SharePointのあったらいいなを集めました(X-SPのご紹介)

2024.02.28

【2024年2月更新】Power Apps の実践的なノウハウ まとめ

モデル駆動型アプリPower AppsPower PlatformSharePointExcelPower AutomateC#attributevalidationローコードAngularAccessInfoPathMatTableAngular Materialデータ構造SortByColumns関数TypeScriptHTMLEF CoreマイグレーションFramework CoreAttribute directivesO/Rマッパーazure sql databaseCase式HTTP RequestCSSxUnit.Net Core 3.1VSCode.Net Core Test ExplorerDataverse for Teamsitem関数Google MapsMarker ClustererRANK()関数Dynamics 365 SalesMicrosoft TranslatorマーカークラスタリングライブラリtailwindcssマルチテナントドロップダウンメニューBreakpointObserverメディアクエリスマホPCレスポンシブ入門初心者中級者キャンバスアプリDatePickerDropdownviewビューアクセス制限承認リマインドSetForAllUpdateContextロードマップ技術It情報技術メッセージIDメールfirst()関数nest入れ子動的リストcollectionコレクション複数の添付ファイル承認フローformエクスポートインポートカスタマイズcomponentダイアログコンポーネントdialogTips新機能変数検索Microsoft 365グループセキュリティグループ送信元メールの送信差出人インスタントクラウドフロー自動化したクラウドフロー委任VBAエラーエクセルerror復元restorePower BI個人列ユーザー列SharePoint Onlineリスト非表示アプリ[市民開発者構築自動化したクラウド フローフローの種類インスタント クラウド フロースケジュール済みクラウド フローレスポンシブ レイアウトresponsive layoutデータ行の制限引き継ぎ退職所有者を変更異動LoopMicrosoftdesignJSONデザインtemplateテンプレート運用選択肢列参照列ChatGPTOpenAIオープンAIチャットGPTgalleryギャラリースクロールコンテナショートカットキーshortcut keyconcat関数文字制限フロー実行開発環境環境本番環境ライセンス環境構築手順pipelineCI/CDパイプラインDevOpsMicrosoft 365簡易在庫管理時間外通知ファイルフィルター クエリドキュメント ライブラリfilter querysortソートmultiple item複数項目シェアポイント便利機能カレンダーCalendarTeams
PageTop
ページトップに戻る