著者:しょっさん
ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。最終回は、本番環境として恥ずかしくないシステムをリリースする方法を解説します。
シェルスクリプトマガジン Vol.70は以下のリンク先でご購入できます。
図3 最終的に実装された各レイヤーのソースコード
■「src/api/expense.ts」の経費精算部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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」の経費精算部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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」の経費精算部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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 アクセス認可を制御する方法
■ロールによって実行するモデル操作を変更する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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」カラムを追加してアクセス認可させる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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} } (略) } } |