著者:しょっさん
ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。最終回は、本番環境として恥ずかしくないシステムをリリースする方法を解説します。
シェルスクリプトマガジン 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}
}
(略)
}
}


