シェルスクリプトマガジン

Webアプリケーションの正しい作り方(Vol.70記載)

著者:しょっさん

ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。最終回は、本番環境として恥ずかしくないシステムをリリースする方法を解説します。

シェルスクリプトマガジン Vol.70は以下のリンク先でご購入できます。

図3 最終的に実装された各レイヤーのソースコード

■「src/api/expense.ts」の経費精算部分

import { Request, Response, NextFunction } from "express";
import Express from "express";
import { ExpenseModel } from "./interfaces/expenseModel";
import { ExpenseController } from "./interfaces/expenseController";

const router = Express.Router();

// DBモデルおよびコントローラのインスタンス化
const expense_model = new ExpenseModel();
const expense_controller = new ExpenseController(expense_model);
// コントローラへ、DBモデルのインスタンスを引き継いでいます

// POST 経費の入力
router.post("/", async (req: Request, res: Response, next: NextFunction) => {
  const result = await expense_controller.submitExpense(req.body!);
  res.send(result);
});
(略)
export default router;

■「src/interfaces/expenseController.ts」の経費精算部分

import { SubmitExpense } from "../usecases/SubmitExpense";
import { IExpenseValue } from "../domains/expenseEntity";
import { ExpenseRepository } from "../adapters/ExpenseRepository";
import { IExpenseModel } from "./IExpenceModel";

export class ExpenseController {
  private expenseRepository: ExpenseRepository;

// DBモデルのインスタンスを基にリポジトリをインスタンス化
  constructor(model: IExpenseModel) {
    this.expenseRepository = new ExpenseRepository(model);
  }

  async submitExpense(
    expense: IExpenseValue
  ): Promise<IExpenseValue> {

    try {
	// リポジトリのインスタンスをユースケースへ引き継いでユースケースを実行
      const usecase = new SubmitExpense(this.expenseRepository);
      const result = await usecase.execute(expense);
      return result.read();
    } catch (error) {
      throw new Error(error);
    }
  }
(略)
}

■「src/adapters/ExpenseRepository.ts」の経費精算部分

import { ExpenseEntity } from "../domains/expenseEntity";
import { IExpenseRepository } from "./IExpenseRepository";
import { IExpenseModel } from "../interfaces/IExpenceModel";

export class ExpenseRepository implements IExpenseRepository {
  private expense_model: IExpenseModel;

	// コントローラから引き継いだDBモデルのインスタンスをここで保持
  constructor(model: IExpenseModel) {
    this.expense_model = model;
  }

  store(expense: ExpenseEntity): Promise<ExpenseEntity> {
	// 特にフォーマットなど変更を今回はしていないので、そのまま保管メソッドをコール
    return this.expense_model.store(expense);
  }
}

図5 アクセス認可を制御する方法

■ロールによって実行するモデル操作を変更する

export class ExpenseModel implements IExpenseModel {
    private _userModel: IUserModel

  constructor(user: IUser) {
    this._userModel = user;
  }
(略)
  findUnapproval(boss_id: number): Promise<ExpenseEntity[]> {
	if (this._userModel.role.find(role => role === 'APPROVER') {
        return Expense.findAll({
          where: Sequelize.literal(
            approval = ${approval_status.unapproved}
          ),
(略)
      } else {
        return Expense.findAll({
          where: Sequelize.literal(
            approval = ${approval_status.unapproved} and user_id IN (SELECT id FROM users WHERE boss_id = '${boss_id}')
          ),
(略)
      }
    }

■経費精算テーブルにアクセス認可用の「role」カラムを追加してアクセス認可させる

export class ExpenseModel implements IExpenseModel {
    private _userModel: IUserModel

  constructor(user: IUser) {
    this._userModel = user;
  }
(略)
  findUnapproval(boss_id: number): Promise<ExpenseEntity[]> {
      return Expense.findAll({
        where: {
          approval: ${approval_status.unapproved}
          role: ${this._userModel.role}
        }
(略)
      }
    }