1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

複数ユーザー向けSaaSのベストプラクティスを考える feat.Angular

Last updated at Posted at 2019-03-25

やりたいこと

エンタープライズ向けのSaaSを構築する上で必ず必要になるマルチユーザー対応。
WEBアプリケーションにおいては、
利用するユーザー(会社)によって挙動・見た目を変える
がいつも課題になっている気がしています。
SPA(Angular)を使った場合、どうしたら開発者もユーザーもハッピーか考えてみる。

やりかた

以下のやり方が考えられるかなーと思います。
①クライアントに設定を持つ
②サーバに設定を持つ

①クライアントに設定ファイルを持つ

Angularの場合だとenvironment.tsのような設定ファイルやSCSSをユーザー別に用意しておき、APIの向き先・挙動などをずらっとかき、ビルド時にユーザーを指定する。

environment-userA.ts
export const environment = {
	production: true,
	user :"usera",
	baseUrl: 'https://usera.application.jp/api/', // APIのベースURL的な
	setteiA: 'A', 
	setteiB: 'B',
...
};
angular.json
	"configurations": {
		"production": {
			"fileReplacements": [
				{
					"replace": "src/environments/environment.ts",
					"with": "src/environments/environment.prod.ts"
				}
			],
			"optimization": true,
			"outputHashing": "all",
			"sourceMap": false,
			"extractCss": true,
			"namedChunks": false,
			"aot": true,
			"extractLicenses": true,
			"vendorChunk": false,
			"buildOptimizer": true,
			"budgets": [
				{
					"type": "initial",
					"maximumWarning": "2mb",
					"maximumError": "5mb"
				}
			]
		},
		"usera": {
			"fileReplacements": [
				{
					"replace": "src/environments/environment.ts",
					"with": "src/environments/environment.userA.ts"
				}
			],
			"optimization": true,
			"outputHashing": "all",
			"sourceMap": false,
			"extractCss": true,
			"namedChunks": false,
			"aot": true,
			"extractLicenses": true,
			"vendorChunk": false,
			"buildOptimizer": true,
			"budgets": [
				{
					"type": "initial",
					"maximumWarning": "2mb",
					"maximumError": "5mb"
				}
			]
		}
	}
},
"serve": {
	"builder": "@angular-devkit/build-angular:dev-server",
	"options": {
		"browserTarget": "sample:build"
	},
	"configurations": {
		"production": {
			"browserTarget": "sample:build:production"
		},
		"userA": {
			"browserTarget": "sample:build:usera"
		}
	}
}

ng build -c usera

PROS

  1. クライアントサイドだけで完結できる
  2. 設定をクライアントソースに内包できる
    CONS
  3. ユーザーごとにoutputが増え、ビルドのコストが高い
  4. 設定変更だけでクライアントの修正&再リリースが必要

②サーバーに設定を持つ

初回リクエスト時に基底コンポーネントから初期サービスを同期でリクエスト。
FQDNに応じてユーザーをリクエストボディにセットした上で初期サービスを呼び出し、レスポンスに応じてCSSや設定を注入する。

app.component.ts
import { Component, OnInit, OnChanges } from '@angular/core';
import { SafeHtml, DomSanitizer } from '@angular/platform-browser';
import { InitService } from 'src/app/services/init.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnChanges {
  public safeHtml: SafeHtml;
  constructor(private domSanitizer: DomSanitizer, private initservice:InitService) {

  }

  async ngOnInit(): Promise<void> {
    // 初期設定サービスを同期で呼び出し。
    this.initialsetting = await this.initservice.init();
    // 各ユーザー別のCSSを初回ロードして&inject。
	this.safeHtml = this.domSanitizer.bypassSecurityTrustHtml("<link rel='stylesheet' type='text/css' href=" + this.initialsetting.cssurl + ">");
  }
    // 適当に設定を保持(storeなりsessionStorageなり)
}

initservice.ts
import { Injectable } from '@angular/core';
import { environment } from '../../environments/environment';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { InitialSetting } from '../models/initialsetting';

@Injectable({
  providedIn: 'root'
})
export class InitService {
  constructor(
    private http: HttpClient,
  ) { }
  
  async init() {
    // host名からユーザコードを持ってくる。mappingはenvironment.tsとかで。
    const host = location.host;
    const userCd = environment.hostToUserCd[host]
    // なにも認証無いとあれなのでヘッダに認証キーを付けとく。
    const httpOptions = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        'Authorization': 'my-auth-token'
      })
    };
    let body = {};
    body['userCd'] = userCd
    const url = 'https://' + host + '/api/initialservice';
    return await this.http.post(url, body, httpOptions)
      .toPromise()
      .then(response => response as InitialSetting)
  }
}

environment.ts
export const environment = {
  production: false,
  hostToUserCd: {
	'usera.application.jp':'usera',
	'userb.application.jp':'userb',
  }
};

PROS

  1. どんなにユーザがいてもビルド一発で終わる
  2. 全ユーザが同じソースで動く(CSS以外)
    CONS
  3. サーバー側に考慮が必要
  4. 初期ロードが若干遅い

まとめ

ユーザが少なければサーバーとクライアントが疎な①型、ユーザが多い場合は②がよいかな。
他にいい案あれば教えてください。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?