Denoでサーバーサイド作らないか？


この記事は トラストバンク Advent Calendar 2022 の3日目の記事です。

はじめまして、 [ふるさとチョイス](https://www.furusato-tax.jp/)のAPI開発を担当している山本です。トラストバンク歴10ヶ月、業務では`php`で`Laravel`使ってます。

トラストバンクでDeno使うのはどうですか？とそれとなく*Deno*（**デノ**）を推す意味も込めて***Deno（とoak）*** で[**クリーンアーキテクチャー**](https://blog.tai2.net/the_clean_architecture.html)で**API**/**~~WEB~~**を作るまでの記事を書きます。本記事では`oak`とクリーンアーキテクチャーに関わらない箇所の説明は簡易なものになります。

Let's ハンズオン

# 事前準備
Denoに触れるのが一年ぶりなのでサラッと環境構築から始めます。細かい説明は省きます。

## 環境構築

### １．WSLを起動
```powershell
PS C:\Users\yamamoto> wsl -d Ubuntu-20.04-deno
```

### ２．deno最新版インストール
```bash
$ asdf plugin-list-all |grep deno
$ asdf plugin-add deno
$ asdf list-all deno
$ asdf install deno 1.28.1
$ mkdir -p ~/src/deno1_28_1-oak
$ cd ~/src/deno1_28_1-oak/
$ asdf local deno 1.28.1
$ asdf plugin-add sqlite
$ asdf install sqlite 3.40.0
$ asdf global sqlite 3.40.0
```

### ３．authbindのインストール
一般ユーザーでport80でListenするプロセスを立ち上げる為です
```
$ sudo apt install authbind
$ sudo toucn /etc/authbind/byport/80
$ sudo chown yamamoto /etc/authbind/byport/80
$ sudo chmod 755 /etc/authbind/byport/80
```

## 動作確認
### １．httpサーバーが立ち上がる事の確認
`deno http`で検索すると[公式マニュアル](https://deno.land/manual@v1.28.1/runtime/http_server_apis)がヒットするのでサンプルコードのポートだけ80に書き換えて使ってみましょう。
```typescript:main.ts
import { serve } from "https://deno.land/std@0.165.0/http/server.ts";
function handler(req: Request): Response {
      return new Response("Hello, World!");
}
serve(handler, { port: 80});
```
起動する

```
$ authbind --deep deno run --allow-net main.ts
```

[http://localhost/](http://localhost/)にアクセスして`Hello, World!`が表示される事を確認します。

### ２．oakでサーバーが立ち上がる事の確認
生Denoではなくoakを使おうと思うので`oak`での動作テストもしてみましょう。

```typescript:main.ts
import { Application } from "https://deno.land/x/oak/mod.ts";

const app = new Application();

// Logger
app.use(async (ctx, next) => {
  await next();
  const rt = ctx.response.headers.get("X-Response-Time");
  console.log(`${ctx.request.method} ${ctx.request.url} - ${rt}`);
});

// Timing
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});

// Hello World!
app.use((ctx) => {
  ctx.response.body = "Hello World!";
});

await app.listen({ port: 80 });
```
:::note info
denoは**Top level await**が効いているので`async`で包んであげる必要はありません。
ES2022も同様に**Top level await**が有効です。
:::

起動してアクセスしてログが出ることを確認
```bash
[yamamoto@deno deno1_28_1-oak]$ authbind --deep deno run --allow-net main.ts
GET http://localhost/ - 0ms
GET http://localhost/favicon.ico - 0ms
```

# 本題

## 要件・機能を考える
~~特に作りたいものが無いので**認証**と**メモ**の二機能作ろうと思います。進捗次第では**メモ**だけになる可能性もあります。~~
時間の都合で下記の3つの実装方法の紹介のみとなりました。
- ユーザーに紐付くメモ（の取得）
- ID/PW認証
- 任意のルートに仕込むミドルウェアの作り方


***[Ushio](https://qiita.com/youhei_seasalt) Architecture*** [^1]（php）を参考にしつつ自由に作りたいと思います。

:::note info
各レイヤー配下に存在する**役者**（Repository等）については[こちらのサイト](http://terasolunaorg.github.io/guideline/current/ja/)、クリーンアーキテクチャーについては[こちらのサイト](https://gist.github.com/mpppk/609d592f25cab9312654b39f1b357c60)とその参照先の[こちらのサイト](https://blog.tai2.net/the_clean_architecture.html)をそれぞれ参考にすると良いでしょう。
:::

## 実装

の前に

:::note warn
この記事は実際にコマンドを打ちながらコーディングしながら調べながら書いています。
その為記事に掲載しているコードと実際のコードに差異がある場合もあります。極力差異の無いようコード修正時は記事に掲載するコードも併せて修正するように気を付けますが、真似てみてもしも手元で動かない場合はコードをよしなに修正して試してください。
:::

### １．import_mapを作る

まずは必要なモジュールを[denoland x](https://deno.land/x)から探します。探すと言っても検索に掛けるだけです。
機能から考えると[oak](https://deno.land/x/oak@v11.1.0)の他には[sqlite](https://deno.land/x/sqlite@v3.7.0)があれば足りるでしょう。[mysql](https://deno.land/x/mysql)や[O/Rマッパー](https://deno.land/x/denodb)もあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982658/85dcce3f-46f9-79b0-ec5a-a5a39e669d00.png)


```json:./import_map.json
{
    "imports": {
      "encoding/": "https://deno.land/std@0.166.0/encoding/",
      "log/": "https://deno.land/std@0.166.0/log/",
      "datetime/": "https://deno.land/std@0.166.0/datetime/",
      "uuid/": "https://deno.land/std@0.166.0/uuid/",
     
      "oak/": "https://deno.land/x/oak@v11.1.0/",
      "sqlite/": "https://deno.land/x/sqlite@v3.7.0/"
    }
  }
```


### ２．エントリーポイントを作る

```typescript:main.ts
//system
import { Application, Router } from "oak/mod.ts";

const router = new Router();
router.get("/", (context: any) => {
  context.response.body = "Hello world! ";
})
router.get("/2", (context: any) => {
  context.response.body = "Hell world! ";
})

const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());

await app.listen({ port: 80 });
```
[http://localhost/](http://localhost/)、[http://localhost/2](http://localhost/2)にアクセスして動作確認する。
容易く二つのルーティングを作る事が出来ました。

現在のディレクトリ構成
```
.
├── .tool-versions
├── .vscode
│   ├── launch.json
│   └── settings.json
├── import_map.json
└── main.ts
```

### ３．Contextの定義
*context*は和訳すると **背景・前後・脈絡・文脈・属性**など出てきますが、プログラムにおける*context*は**文脈**です。
今回作る機能は認証認可とメモと決めたのでその二つの***context*ディレクトリ**を作ります。
```bash
mkdir -p ./contexts/account
mkdir ./contexts/memo
```

### ４．レイヤーと概念(役割/機能)の定義
コンテキスト配下のレイヤーは`Domain / Infrastructure / Application / (Web)UI`など考えられます。なお概念とは`Entity / Repository`などを指します。

```bash
mkdir -p ./contexts/account/{domain,infrastructure,use_case}
mkdir -p ./contexts/account/domain/{entity,exception,persistence}
mkdir -p ./contexts/account/infrastructure/{persistence,presenter}
mkdir -p ./contexts/account/application/use_case
mkdir -p ./contexts/account/application/controller

mkdir -p ./contexts/memo/{domain,infrastructure,use_case}
mkdir -p ./contexts/memo/domain/{entity,exception,persistence}
mkdir -p ./contexts/memo/infrastructure/{persistence,presenter}
mkdir -p ./contexts/memo/application/use_case
mkdir -p ./contexts/memo/application/controller
```

### ５．メモのEntityを作る
[クリーンアーキテクチャ(The Clean Architecture翻訳)](https://blog.tai2.net/the_clean_architecture.html)によるとEntityは

> エンティティは、メソッドを持ったオブジェクトかもしれない、あるいは、データ構造と関数の集合かもしれない。

との事ですが、***Ushio Architecture***は後者です。
また、レイヤー越えるデータは

> 典型的には、境界をまたがるデータは、シンプルなデータ構造だ。基本的な構造体や、シンプルなデータ転送オブジェクト(Data Transfer object)を好みに応じて使うことができる。

とも書いてありますね。

脳内設計で早速作りましょう。`typescript`は型定義が出来るのでEntityとTypeを別々（前者）で作ってみます。ついでにコマンドラインから簡単に試せるように`import.meta.main`も追加しましょう。pythonで言うところの`__name__ == '__main__'`です。

:::note warn
ボブが言うには**Enterprise wide**なビジネスロジックは**Entity**に、**アプリケーションの固有**のビジネスロジックは**UseCase**に書くというルールとの事ですが、*Enterprise Wide*をどう和訳すれば良いのか分からず責務の判断がつかないので、*Ushio Architecture*を参考に雰囲気で作ります。
:::

後でメソッドに手を加えるでしょうが、ひとまずこんな感じです。
```typescript:vim ./contexts/memo/domain/entity/memo.ts

//type Constructor<T> = { new (): T };
export type Id = number;
export type UserId = number;
export type Content = string;
export type LockNo = number;
export type Updater = string;
export type Updated = Date;
export type Data = {
    id: Id|undefined,
    userId: UserId,
    content: Content,
    lockNo: number,
    updater: string,
    updated?: Date,
    //created?: Timestamp
};

export class Memo {
    
    private data: Data;
    
    private constructor(data: Data)
    {
        this.data = data;
    }

    /*
    static duplicate<T>(
        this: Constructor<T>,
    ): Memo[] {
        return [];
    }
    */

    static async create<T extends MemoRepository>(data:Data, memoRepository: T): Promise<Memo|false>//deno 1.28のlinterではnewが使えないアピールしてくるので
    {
        const result = await memoRepository.create(data);
        if (result === false) {
            return new Promise( (resolve) => resolve(false) );
        } else {
            return new Promise( (resolve) => resolve(new this(result)) );
        }
    }

    static restore(data: Data): Memo {
        try{
            if ('id' in data === false) {
                throw new Error("id undefined");
            }
            if ('lockNo' in data === false) {
                throw new Error("lockNo undefined");
            }
            if ('updated' in data === false) {
                throw new Error("updated undefined");
            }    
        } catch(e) {
            throw new Error(e);
        }
        return new this(data);
    }

    getData (): Data{
        return this.data;
    }
}


if (import.meta.main) {
    const restore = Memo.restore()
    console.log(restore);
    const create = Memo.create(
        12,//userid
        "content",//content
        2,//lockno
        "memo.restore"//updater
    );
    console.log(create);
    console.log(create.getData());
}
```

### ６．repositoryを作る

先に挙げた参考サイトの引用元によると
> リポジトリのインターフェースはEnterprise Business Rulesで実装されなければなりません。しかし、Application Business Rulesでしか利用しないリポジトリであれば、そちらでインターフェースを実装することも可能です。(TODO: 実装例について、ドメイン層とusecase層それぞれでどのようにリポジトリを実装しているか確認)

との事です。
今回の構成だと`application`配下か`domain`配下のどっちに置くかって話ですね。では作ります。

#### リポジトリーのインターフェイス
```typescript:vim ./contexts/memo/domain/persistence/memo_repository.ts
import {Id, UserId, Data} from "../entity/memo.ts";

export interface MemoRepository {
    get(id: Id): Promise<Data|false>;
    getByUserId(userId: UserId): Promise<Array<Data>>;
    getAll(): Promise<Array<Data>>;
    create(memo: Data): Promise<Data|false>;
    update(memo: Data): Promise<boolean>;
}
```
Wiki見たところ
> 非同期イベント駆動型プラットフォームとしてlibuv（英語版）に代わってTokioが導入され

と書いてあるので***Deno***も***nodejs***同様にノンブロッキングI/Oなので`Promise`で作りましょう。Promise包むだけなので簡単ですね、簡単に使えるものは何でも使いましょう。

:::note warn
今回は`Promise`にする意味は無いです。
掲載コードを真似たらもしかするとスピードマニアに無駄なオーバーヘッドだと指摘されるかもしれません。指摘であれば根拠と証拠のデータが伴うはずなので、**何がどういう理由で何と比べてどの程度遅くなる**のかを質問してみましょう。レベルアップのチャンスです。（教えてもらえれば）
:::

#### リポジトリーの実装の前に
実装する前にはまずデータの保存先が必要です。sqliteでMemoテーブル作りましょう。Entityに対応するテーブル定義でOKです。
`deno migration`で検索したら[denolandのnessie](https://deno.land/x/nessie@2.0.10)が真っ先に引っかかっりました。[github](https://github.com/halvardssm/deno-nessie)の**Stars**と**コミットログ**を見た感じ使っても良さそうな気がするので調べながら使っちゃいましょう。

```bash:bash
deno install --unstable --allow-net=localhost --allow-read=. --allow-write=nessie.config.ts,db -f  https://deno.land/x/nessie/cli.ts
deno run -A --unstable https://deno.land/x/nessie/cli.ts init --mode config --dialect sqlite
mkdir -p ./db/migrations
mkdir -p ./db/seeds
deno run -A --unstable https://deno.land/x/nessie@2.0.10/cli.ts make create_memos
```

マイグレーションファイルの修正
```
割愛
```

マイグレーション
```bash:migrate
$ deno run -A --unstable https://deno.land/x/nessie/cli.ts migrate

#rollback
$ deno run -A --unstable https://deno.land/x/nessie/cli.ts rollback
```

DB確認
```bash
$ sqlite3 sqlite.db
sqlite> .tables
sqlite> .schema memos
CREATE TABLE memos (id integer primary key, user_id integer, content text, lock_no integer, updater text, created timestamp, updated timestamp default (datetime(CURRENT_TIMESTAMP,'localtime')));

sqlite> insert into memos (user_id, content, lock_no, updater) values (100, '試験データ', 1, 'sqlite client');
sqlite> insert into memos (user_id, content, lock_no, updater) values (101, '時間がピンチ', 1, 'sqlite client');
sqlite> insert into memos (user_id, content, lock_no, updater) values (101, '時間がない！', 1, '手動インサート');
sqlite> select * from memos;
1|100|テ試験データ|1|sqlite client||2022-11-27 11:45:19
```

:::note info
マイグレーションしない場合は**DB確認**に乗せたSQL参考にしてください。
:::

#### リポジトリーの実装
denoのモジュールの[example](https://deno.land/x/sqlite@v3.7.0#example)を参考にしながら作ります。

:::note alert
普通に作るともうアドベントカレンダーの公開日に間に合わなさそう:skull:なので、これ以降の作業は決め打ちしたり共通化しなかったりで作ります。
:::

```typescript:vim ./contexts/memo/infrastructure/persistence/memo_repository_impl_sqlite.ts
import * as log from "log/mod.ts";
import { DB } from "sqlite/mod.ts";
import { Data, Id, UserId } from "../../domain/entity/memo.ts";
import { MemoRepository } from "../../domain/persistence/memo_repository.ts";

type SelectColumns = {
    id: number,
    user_id: number,
    content: string,
    lock_no: number,
    updater: string,
    updated: Date,
};

type InsertPlaceholder = {
    userId: number,
    content: string,
    lockNo: number,
    updater: string,
}

export class MemoRepositoryImplSqlite implements MemoRepository {

    async get(id: Id): Promise<Data|false> {

        try {
            const db = new DB("sqlite.db");
            const query = db.prepareQuery<
                    [number, number, string, number, string, Date],
                    SelectColumns
                >("SELECT id, user_id, content, lock_no, updater, updated FROM memos WHERE id = :id");
            //const query = db.prepareQuery("SELECT id, user_id, content, lock_no, updater, updated FROM memos WHERE id = :id");
            const rows = query.allEntries({ id });

            if (rows.length !== 1) {
                log.warning("Finded rows count 0 or greater than 2");
                return new Promise((resolve)=>resolve(false));
            } else {
                log.info("Finded rows count 1");
                //const row: Columns = rows[0] as unknown as Columns;
                const row = rows[0];
   
                const result: Data = {
                    id: row['id'],
                    userId: row['user_id'],
                    content: row['content'],
                    lockNo: row['lock_no'],
                    updater: row['updater'],
                    updated: row['updated']
                };

                log.info(result);
                return new Promise((resolve)=>resolve(result));
            }
    
        } catch (e) {
            log.error(`Exception ${e}`);
            throw new Error(e.message);
            //throw new Error(e.code);
            //throw new Error(e.codeName);
        }
    }

    async getByUserId(userId: UserId): Promise<Array<Data>> {
        //実装済み
    }

    async getAll(): Promise<Array<Data>> {
        //実装済み
    }

    async create(memo: Data): Promise<Data|false> {
        //実装済み
    }

    async update(memo: Data): Promise<boolean> {
        throw new Error("Method not implemented.");
    }

}

if (import.meta.main) {
    // deno run --importmap import_map.json --allow-all ./contexts/memo/infrastructure/persistence/memo_repository_impl_sqlite.ts
    (async ()=> {      
        const repo = new MemoRepositoryImplSqlite();

        if (false) {
            //get test
            const res = await repo.get(1);
            log.debug(res);
            const res2 = await repo.get(2);
            log.debug(res2);
            const res3 = await repo.get(100);
            log.debug(res3);    
        }

        if (false) {
            //getByUserId test
            const list = await repo.getByUserId(100);
            log.debug(list);
            const list2 = await repo.getByUserId(101);
            log.debug(list2);    
        }

        if (true) {
            //create test
            const data: Data = {
                userId: 789,
                content: "ResultをInsertDataに",
                updater: "Command line"
            };
            const createResult = await repo.create(data);
            log.error(createResult);
        }

        if (true) {
            //getAll test
            const all = await repo.getAll();
            log.critical(all);    
        }
    })();
}

```

:::note
記載のソースを参考に`getAll`・`getByUserId`・`create`を実装して下さい。以降の工程で使います。
:::

**動作確認**
```bash:
$ deno run --importmap import_map.json --allow-all ./contexts/memo/infrastructure/persistence/memo_repository_impl_sqlite.ts
~~省略~~
INFO [{"id":2,"userId":101,"content":"時間がピンチ","lockNo":1,"updater":"sqlite client","updated":"2022-11-27 15:23:27"},{"id":3,"userId":101,"content":"時間がない！","lockNo":1,"updater":"手動インサー ト","updated":"2022-11-27 15:52:31"}]
DEBUG [{"id":2,"userId":101,"content":"時間がピンチ","lockNo":1,"updater":"sqlite client","updated":"2022-11-27 15:23:27"},{"id":3,"userId":101,"content":"時間がない！","lockNo":1,"updater":"手動インサート","updated":"2022-11-27 15:52:31"}]
```

### ７．usecaseを考える

usecaseは**インターフェイスを用意するもの**らしいですが、今回は用意しません。
各コンテキストで考えられるusecaseは下記でしょう。

- アカウント
    - ~~作成(登録)~~ (store) ※今回は作らない
    - ~~認証~~ (authentication) ※今回は作れない（時間が）
    - ~~認可~~ (authorization) ※今回は作れない（時間が）
    - ~~削除~~ (delete) ※今回は作らない
- メモ
    - 一覧 (list)
    - 新規作成 (store)
    - ~~更新~~ (update) ※今回は作れない（時間が）
    - ~~削除~~ (delete) ※今回は作れない（時間が）

#### usecaseに合わせたディレクトリを作成する
```bash
mkdir ./contexts/account/application/use_case/{store,authentication,authorization,delete}
mkdir ./contexts/memo/application/use_case/{list,store,update,delete}
```

#### メモ一覧表示用のusecaseの使い道
実装（interactor）とinputとoutputを作ります。
```bash
$ touch ./contexts/memo/application/use_case/list/{input,interactor,output}.ts
$ touch ./contexts/memo/application/use_case/store/{input,interactor,output}.ts
```

#### inputの実装
```typescript:vim ./contexts/memo/application/use_case/list/input.ts
import { UserId } from "../../../domain/entity/memo.ts";

export type Input = {
    userId: UserId
};

export function input(params: any): Input {
    return { userId: params.user_id};
}
```

#### interactorの実装
oakには手軽にDI（`constructor dependency injection`）を実現する方法は無さそうです。比較的簡単そうなのは[tsyringe](https://github.com/microsoft/tsyringe)を取り入れる事でしょうか。今回は出来るだけ**あの円**の外側から注入する事だけ意識します。クラスである必要も無いので`function`で定義します。

単発で動かせるコードも書きます。

```typescript:vim ./contexts/memo/application/use_case/list/interactor.ts
import { Memo } from "../../../domain/entity/memo.ts";
import { MemoRepository } from "../../../domain/persistence/memo_repository.ts";
import { Input } from "./input.ts";

export async function execute<T extends MemoRepository>(input: Input, memoRepository: T): Promise<Array<Memo>> {
    return (await memoRepository.getByUserId(input.userId)).map( (data)=> Memo.restore(data));
}

import { MemoRepositoryImplSqlite } from "../../../infrastructure/persistence/memo_repository_impl_sqlite.ts";
if (import.meta.main) {
    (async()=>{
        const input:Input = {
            userId: 101
        };
        const repo = new MemoRepositoryImplSqlite();
        await execute(input, repo);
    })();
}
```

#### outputの実装
```typescript:vim ./contexts/memo/application/use_case/list/output.ts
import { Id, UserId, Content, Memo } from "../../../domain/entity/memo.ts";

export type Output = {
    id: Id
    userId: UserId
    content: Content
};

export function output(memos: Array<Memo>): Array<Output> {
    const output: Array<Output> = [];
    memos.forEach((v: Memo)=> output.push(
        {
            id: v.getData().id as Id,
            userId: v.getData().userId as UserId,
            content: v.getData().content as Content,
        }        
    ));
    return output;
}

if (import.meta.main) {
    (async()=>{
        const memo1 = Memo.restore({
            id: 4,
            userId: 2,
            content: "Content",
            lockNo: 22,
            updater: "string",
            updated: new Date(),
        });
        const memo2 = Memo.restore({
            id: 10,
            userId: 2,
            content: "コンテンツ",
            lockNo: 22,
            updater: "string",
            updated: new Date(),
        });
        const input: Array<Memo> = [memo1, memo2];
        const result = await output (input);
        console.log(result);
    })();
}
```

### ８．controllerを用意する
インバウンドを受け取り各ユースケースへ渡して返してもらう役割。**Enterprise wide**なバリデーションもここです。
- APIの場合はjsonで入ってjsonで出ていく
- ブラウザーの場合はフォームによるPOSTなどで入って来てhtmlで出ていく

という事は、`api/xxx_controller`と`web/xxx_controller`を用意すれば良いと考える事が出来ます。
```bash
mkdir ./contexts/account/application/controller/{api,web}
touch ./contexts/account/application/controller/api/{store,authentication,authorization,delete}_controller.ts
touch ./contexts/account/application/controller/web/{store,authentication,authorization,delete}_controller.ts
mkdir ./contexts/memo/application/controller/{api,web}
touch ./contexts/memo/application/controller/api/{list,store,update,delete}_controller.ts
touch ./contexts/memo/application/controller/web/{list,store,update,delete}_controller.ts
```

早速実装します。ここでやっと***oak***が絡んできます。

```typescript:vim api/list_controller.ts
import {Status, helpers} from "oak/mod.ts";
import * as log from "log/mod.ts";
import { Response, ResponseBody } from "../../../../../common/response.ts";
import { input } from "../../use_case/list/input.ts";
import { output } from "../../use_case/list/output.ts";
import { execute } from "../../use_case/list/interactor.ts";
import { MemoRepositoryImplSqlite } from "../../../infrastructure/persistence/memo_repository_impl_sqlite.ts";

export default async function controller(ctx: any) {

    const memoRepository = new MemoRepositoryImplSqlite();

    const inbound = ctx.params;
    //Todo: validate(inbound)
    const outbound = output(await execute(input(inbound), memoRepository));
}
```
引数の`ctx`が***oak***が定義する型です。

### ｅｘ１．レスポンス生成用の共通処理を作る
レスポンスのフォーマットを統一する為にひとつクラスを作ります。
```bash
$ mkdir -p ./common
$ touch  ./common/response.ts
```
```typescript:vim ./common/response.ts
export type StatusCode = number;
export type StatusName = string;
export type ResponseMessage = string;
export type ResponseType = string;//json,html,text,other...
export type ResponseData = {
    status: StatusName,
    message: ResponseMessage,
    data: object,
};
export type Status = number;
export type Type = string;

export class ResponseBody {
    
    private status!: StatusName;
    private message!: ResponseMessage;
    private data!: object;

    getData(): ResponseData{
        return {
            status: this.status,
            message: this.message,
            data: this.data,
        }
    }

    static create(data: ResponseData){
        const newSelf = new ResponseBody();
        newSelf.status = data.status;
        newSelf.message = data.message;
        newSelf.data = data.data;
        return newSelf
    }
}

export class Response {

    private status!: StatusCode;
    private type!: ResponseType;
    private body!: ResponseData;

    getStatus() {
        return this.status;
    }

    getType() {
        return this.type;
    }

    getBody() {
        return this.body;
    }

    static create(
        {status, type, body}: {status: StatusCode, type: ResponseType, body: ResponseData}
    ): Response{
        const newSelf = new Response();
        newSelf.status = status;
        newSelf.type = type;
        newSelf.body = body;
        return newSelf;
    }
}
```

### ９．presenterを作~~る~~らない
役割は前述の通りです。時間の都合で今回は省略します。とは言うものの役割の重さ的にapiとwebの各コントローラー内部に書いても問題無いでしょう。早速コントローラーを作り変えます。
**ここでもoakが絡んで来ます。**
oakのテストの書き方を調べてみたところ、[こんな感じ](https://oakserver.github.io/oak/testing.html)で書けそうなので適当に引用して作ってみます。
ログは出してませんがリポジトリーのソースに書かれたロガーが動いてくれるのでそれで確認する事としましょう。

```typescript: list_controller.ts
import {Status} from "oak/mod.ts";
import { Response, ResponseBody } from "../../../../../common/response.ts";
import { input } from "../../use_case/list/input.ts";
import { output } from "../../use_case/list/output.ts";
import { execute } from "../../use_case/list/interactor.ts";
import { MemoRepositoryImplSqlite } from "../../../infrastructure/persistence/memo_repository_impl_sqlite.ts";

export default async function controller(ctx: any) {

    const memoRepository = new MemoRepositoryImplSqlite();

    const inbound = ctx.params;
    //Todo: validate(inbound)
    const outbound = output(await execute(input(inbound), memoRepository));

    const responseBody = ResponseBody.create({
        status: "処理結果のステータスコード（任意）",
        message: "処理結果を示すメッセージ",
        data: outbound
    });
    const response = Response.create({
        status: Status.OK,
        type: "json",
        body: responseBody.getData()
    });
    
    ctx.response.status = response.getStatus();
    ctx.response.type = response.getType();
    ctx.response.body = response.getBody();
    return ;
}

import { testing } from "oak/mod.ts";
if (import.meta.main) {
    (async()=>{
        const ctx = testing.createMockContext({
            path: "/memo/list",
            params: {
                user_id: "101"
            }
          });    
        await controller(ctx);
        const ctx2 = testing.createMockContext({
            path: "/memo/list",
            params: {
                user_id: "101"
            }
          });    
        await controller(ctx2);
    })();
}
```
動作確認してみましょう
```bash
$ deno run --importmap import_map.json --allow-all ./contexts/memo/application/controller/api/list_controller.ts
INFO Function getByUserId
INFO result
INFO [{"id":1,"userId":100,"content":"試験データ","lockNo":1,"updater":"sqlite client","updated":"2022-11-27 15:22:20"}]
INFO Function getByUserId
INFO result
INFO [{"id":2,"userId":101,"content":"時間がピンチ","lockNo":1,"updater":"sqlite client","updated":"2022-11-27 15:23:27"},{"id":3,"userId":101,"content":"時間がない！","lockNo":1,"updater":"手動インサー ト","updated":"2022-11-27 15:52:31"}]
```
OK、動きました。
ところで、これらのテストコードはDenoのtestを使って書く事も出来ます。その場合はvscode上でワンクリックでテストをする事も出来ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982658/688d3958-5eff-640f-396d-d230f36859aa.png)


### １０．ルーティングを設定する
:::note
この辺りから**さらに**`oak`成分多めの領域です。
:::
#### accountのルーティングを考える。
<details><summary>考えた結果</summary>

| input | path | method | controller |
|:--|:--|:--|:-:|
| api | account/list | get | list_controller.ts |
| api | account/store | post  | store_controller.ts |
| api | account/authentication | post | authentication_controller.ts |
| api | account/authorization | post | authorization_controller.ts |
| web | account/list | get | list_controller.ts |
| web | account/store | post | store_controller.ts |
| web | account/authentication | post | authentication_controller.ts |
| web | account/authorization | post | authorization_controller.ts |
</details>

#### memoのルーティングも考える
<details><summary>考えた結果</summary>

| input | path | method | controller |
|:--|:--|:--|:-:|
| api | memo/list | get | list_controller.ts |
| api | memo/store | post  | store_controller.ts |
| api | memo/update | post | update_controller.ts |
| api | memo/delete | post | delete_controller.ts |
| web | memo/list | get | list_controller.ts |
| web | memo/store | post | store_controller.ts |
| web | memo/update | post | update_controller.ts |
| web | memo/delete | post | delete_controller.ts |
</details>

#### ルーティングファイルを用意する。

```bash
mkdir ./routes/
touch ./routes/api.ts
touch ./routes/web.ts
```

時間的制約でメモの全機能を作れる気がしないのでパス`/memo`だけ定義します。
```typescript:vim ./routes/api.ts
import { Router } from "oak/mod.ts";
import memoListController from "../contexts/memo/application/controller/api/list_controller.ts";

const router = new Router();
router.get("/memo", memoListController);

export default router;
```

#### エントリーポイントを修正する
サーバーを起動する時のファイルです。序盤でoakの動作確認した時のものです。
```typescript:vim ./main.ts
import { Application, Router } from "oak/mod.ts";
import api from "./routes/api.ts";

const app = new Application();
app.use(api.routes());
app.use(api.allowedMethods());
await app.listen({ port: 80 });
```

起動して
```bash
$ authbind --deep deno run --importmap import_map.json --allow-all main.ts
```
アクセスすると
```bash
$ curl -s http://localhost/memo?user_id=101|jq
```
user_idで指定したユーザーのメモがjsonで返って
```json
{
  "status": "000",
  "message": "Successed",
  "data": []
}
```
**来ません。**

調べてみたところ、どうやら今のcontrollerの書き方ではクエリーパラメーターを受け取れない事が分かりました。
早速修正します。

```typescript
export default async function controller(ctx: any) {
    const memoRepository = new MemoRepositoryImplSqlite();
    const inbound = helpers.getQuery(ctx);
    const outbound = output(await execute(input(inbound), memoRepository));
    const responseBody = ResponseBody.create({
        status: "000",//システムで定義したいであろうコード
        message: "Successed",//Statusを言葉で端的に説明したもの
        data: outbound
    });
    const response = Response.create({
        status: Status.OK,
        type: "json",
        body: responseBody.getData()
    });
    ctx.response.status = response.getStatus();
    ctx.response.type = response.getType();
    ctx.response.body = response.getBody();
    return ;
}
```
もう一度アクセスすると
```bash
$ curl -s http://localhost/memo?user_id=101|jq
```
jsonが返って来ます。
```json
{
  "status": "000",
  "message": "Successed",
  "data": [
    {
      "id": 2,
      "userId": 101,
      "content": "時間がピンチ"
    },
    {
      "id": 3,
      "userId": 101,
      "content": "時間がない！"
    }
  ]
}
```
他人のuser_idさえ指定すれば勝手に他人のメモを見る事が出来るロクでもないAPIの完成です。

**参考までに、ここまでの時点でのツリー構造は下記の通りです。**

<details><summary>tree</summary>

```
.
├── common
│   └── response.ts
├── contexts
│   ├── account
│   │   ├── application
│   │   │   ├── controller
│   │   │   │   ├── api
│   │   │   │   │   ├── authentication_controller.ts
│   │   │   │   │   ├── authorization_controller.ts
│   │   │   │   │   ├── delete_controller.ts
│   │   │   │   │   └── store_controller.ts
│   │   │   │   └── web
│   │   │   │       ├── authentication_controller.ts
│   │   │   │       ├── authorization_controller.ts
│   │   │   │       ├── delete_controller.ts
│   │   │   │       └── store_controller.ts
│   │   │   └── use_case
│   │   │       ├── authentication
│   │   │       ├── authorization
│   │   │       ├── delete
│   │   │       └── store
│   │   ├── domain
│   │   │   ├── entity
│   │   │   ├── exception
│   │   │   └── persistence
│   │   └── infrastructure
│   │       ├── persistence
│   │       └── presenter
│   └── memo
│       ├── application
│       │   ├── controller
│       │   │   ├── api
│       │   │   │   ├── delete_controller.ts
│       │   │   │   ├── list_controller.ts
│       │   │   │   ├── store_controller.ts
│       │   │   │   └── update_controller.ts
│       │   │   └── web
│       │   │       ├── delete_controller.ts
│       │   │       ├── list_controller.ts
│       │   │       ├── store_controller.ts
│       │   │       └── update_controller.ts
│       │   └── use_case
│       │       ├── delete
│       │       ├── list
│       │       │   ├── input.ts
│       │       │   ├── interactor.ts
│       │       │   └── output.ts
│       │       ├── store
│       │       │   ├── input.ts
│       │       │   ├── interactor.ts
│       │       │   └── output.ts
│       │       └── update
│       ├── domain
│       │   ├── entity
│       │   │   └── memo.ts
│       │   ├── exception
│       │   └── persistence
│       │       └── memo_repository.ts
│       └── infrastructure
│           ├── persistence
│           │   └── memo_repository_impl_sqlite.ts
│           └── presenter
│               ├── html.ts
│               └── json.ts
├── db
│   ├── migrations
│   │   └── 20221127111118_create_memos.ts
│   └── seeds
├── import_map.json
├── log.txt
├── main.ts
├── nessie.config.ts
├── routes
│   ├── api.ts
│   └── web.ts
└── sqlite.db
```
</details>

### ｅｘ２．不変にする

実は今のentityの実装、php感覚だと一見privateにカプセル化されているように見えますが全くの無意味です。entityに実装したgetDataも何の意味もありません。メソッドとデータ型を別で定義した意味もありません。これを直します。

:::note info
[プライベートフィールド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes/Private_class_fields)を用いればphp感覚で作っても意図通りに動作してくれるかもしれませんが、未確認なので今回は使いません。
:::

#### createメソッド修正
```typescript:before
    static async create<T extends MemoRepository>(data:Data, memoRepository: T): Promise<Memo|false>//deno 1.28のlinterではnewが使えないアピールしてくるので
    {
        const result = await memoRepository.create(data);
        if (result === false) {
            return new Promise( (resolve) => resolve(false) );
        } else {
            return new Promise( (resolve) => resolve(new this(result)) );
        }
    }
```

```typescript:after
    static async create<T extends MemoRepository>(data:Data, memoRepository: T): Promise<Data|false>//deno 1.28のlinterではnewが使えないアピールしてくるので
    {
        const result = await memoRepository.create(data);
        if (result === false) {
            return new Promise( (resolve) => resolve(false) );
        } else {
            return new Promise( (resolve) => resolve(Object.freeze(result)) );
        }
    }
```
#### restoreを検証用メソッドに変更
```typescript:before
    static restore(data: Data): Memo {
        try{
            if ('id' in data === false) {
                throw new Error("id undefined");
            }
            if ('lockNo' in data === false) {
                throw new Error("lockNo undefined");
            }
            if ('updated' in data === false) {
                throw new Error("updated undefined");
            }    
        } catch(e) {
            throw new Error(e);
        }
        return new this(data);
    }
```
```typescript:after
    /**
     * DBが返したデータである事を検証し、正当であれば不変にして返す
     * 
     * @param data 
     * @returns 
     */
    static validate(data: Data): Data {
        try{
            if ('id' in data === false) {
                throw new Error("id undefined");
            }
            if ('lockNo' in data === false) {
                throw new Error("lockNo undefined");
            }
            if ('updated' in data === false) {
                throw new Error("updated undefined");
            }    
        } catch(e) {
            throw new Error(e);
        }
        return Object.freeze(data);
    }
```
これでプライベートフィールドもコンストラクターも不要になり且つ**イミュータブル（不変）** になりました。メソッドと型の分離完了です。**この修正に伴って関係各ファイルも修正が必要です。直しましょう。**
<details><summary>関係する箇所</summary>

```typescript: リポジトリー
//～省略～
    async get(id: Id): Promise<Data|false> {

        log.info("Function get");
        try {
            const db = new DB("sqlite.db");
            const query = db.prepareQuery<
                    [number, number, string, number, string, Date],
                    SelectColumns
                >("SELECT id, user_id, content, lock_no, updater, updated FROM memos WHERE id = :id");
            //const query = db.prepareQuery("SELECT id, user_id, content, lock_no, updater, updated FROM memos WHERE id = :id");
            const rows = query.allEntries({ id });

            if (rows.length !== 1) {
                log.warning("Finded rows count 0 or greater than 2");
                return new Promise((resolve)=>resolve(false));
            } else {
                log.info("Finded rows count 1");
                //const row: Columns = rows[0] as unknown as Columns;
                const row = rows[0];
                log.info("rows");
                log.info(row);
   
                const result: Data = {
                    id: row['id'],
                    userId: row['user_id'],
                    content: row['content'],
                    lockNo: row['lock_no'],
                    updater: row['updater'],
                    updated: row['updated']
                };

                log.info("result");
                log.info(result);
                return new Promise((resolve)=>resolve(Object.freeze(result) ));
            }
    
        } catch (e) {
            log.error(`Exception ${e}`);
            throw new Error(e.message);
            //throw new Error(e.code);
            //throw new Error(e.codeName);
        }
    }

    async getByUserId(userId: UserId): Promise<Array<Data>> {

        log.info("Function getByUserId");
        try {
            const db = new DB("sqlite.db");
            const query = db.prepareQuery<
                    [number, number, string, number, string, Date],
                    SelectColumns
                >("SELECT id, user_id, content, lock_no, updater, updated FROM memos WHERE user_id = :userId");
            const rows = query.allEntries({ userId });
            const result: Array<Data> = rows.map((row)=>{
                return {
                    id: row['id'],
                    userId: row['user_id'],
                    content: row['content'],
                    lockNo: row['lock_no'],
                    updater: row['updater'],
                    updated: row['updated']    
                }
            });

            log.info("result");
            log.info(result);
            const freezedResult = result.map((row)=>Memo.validate(row));
            return new Promise((resolve)=>resolve(freezedResult));
    
        } catch (e) {
            log.error(`Exception ${e}`);
            throw new Error(e.message);
        }
    }
//～省略～
```
```typescript:インタラクター
//before
export async function execute<T extends MemoRepository>(input: Input, memoRepository: T): Promise<Array<Data>> {
    return (await memoRepository.getByUserId(input.userId)).map( (data)=> Memo.validate(data));
}

//after
export async function execute<T extends MemoRepository>(input: Input, memoRepository: T): Promise<Array<Data>> {
    return await memoRepository.getByUserId(input.userId);
}

```

</details>


### １１．認証関連を作る

それではロクでもない状態を直す為にも認証を作ります。

各レイヤーの説明はここまでの工程で済んでるので省略しますが、やる事は
1. accountsテーブルを作る
1. accountsのレコードを用意
1. 認証を作る
1. 認可を作る
1. 任意のルートに差し込めるミドルウェアにする
1. メモのルーティングに差し込む

です。
厄介そうなのはミドルウェアにする所ですが、何とかなりそうです。([https://oakserver.github.io/oak/](https://oakserver.github.io/oak/))
新しい要素はミドルウェアにする点とそれをルーティングに差し込む点の二つだけです。それ以外についてはここまでの説明で足りていると思いますし設計も言語に依らず共通だと思うので解説は雑に、コードもほとんど省略でいきます。[^2]

#### accountsテーブルの作成
前述のマイグレーションでも`CREATE`直打ちでも何でも良いので、`Seq/ID/PW`のカラムを含めて作りましょう。

#### accountsのレコードを用意
ユーザー情報を適当にINSERTします。**今回のところ**はPWは平文にしておきます。
```sql
sqlite> insert into accounts (user, password) values ('yamamoto', 'yamapass');
sqlite> insert into accounts (user, password) values ('umimoto', 'umipass');
sqlite> select * from accounts;
1|yamamoto|yamapass||||2022-12-01 00:25:10
2|umimoto|umipass||||2022-12-01 00:25:35
```

:::note alert

Denoland.xには[bcrypt](https://deno.land/x/bcrypt@v0.4.1)を作れるモジュールがあります。公開するサービスを開発する時は最低限これを使いましょう。
:::

#### 認証を作る
ID/PWを検証し、正当であればセッションやJWTやアクセストークンを返す機能を作ります。

##### domain layer
<details><summary>認証用エンティティ</summary>

```typescript:cat ./contexts/account/domain/entity/user.ts
import { AccountRepository } from "../persistence/account_repository.ts";
export type Id = number;
export type User = string;
export type Password = string;
export type LockNo = number;
export type Updater = string;
export type Updated = Date;
export type Data = {
    id?: Id,
    user: User,
    password: Password,
    lockNo?: number,
    updater: string,
    updated?: Date,
    created?: Date
};

export class Account {

    static async create<T extends AccountRepository>(data:Data, AccountRepository: T): Promise<Data|false>
    {
        const result = await AccountRepository.create(data);
        if (result === false) {
            return new Promise( (resolve) => resolve(false) );
        } else {
            return new Promise( (resolve) => resolve(Object.freeze(result)) );
        }
    }

    static validate(data: Data): Data {
        try{
            if ('id' in data === false) {
                throw new Error("id undefined");
            }
            if ('lockNo' in data === false) {
                throw new Error("lockNo undefined");
            }
            if ('updated' in data === false) {
                throw new Error("updated undefined");
            }    
        } catch(e) {
            throw new Error(e);
        }
        return Object.freeze(data);
    }
}
```
</details>


<details><summary>認可用エンティティ</summary>

```typescript:認可用
export type User = string;
export type AccessToken = string;
export type Expired = number;
export type Data = {
    user: User,
    accessToken: AccessToken
    expired: Expired
};
export class Authentication {

    static create(user:User, expired: Expired = 0): Data
    {
        const accessToken = "本当は発行する処理が必要";//作りましょう
        const result = {
            user,
            accessToken,
            expired
        };
        return Object.freeze(result);
    }

    static validate(data: Data): boolean {
        return true;//真面目なバリデーションを作りましょう
    }
}
```
</details>


<details><summary>リポジトリーインターフェイス</summary>

```typescript:cat ./contexts/account/domain/persistence/account_repository.ts
import {Id, User, Data, Password} from "../entity/user.ts";

export interface AccountRepository {
    get(id: Id): Promise<Data|false>;
    getByUserPassword(user: User, password: Password): Promise<Data|false>;
    getAll(): Promise<Array<Data>>;
    create(account: Data): Promise<Data|false>;
    update(account: Data): Promise<boolean>;
}
```
</details>

##### infrastructure layer
<details><summary>リポジトリー</summary>

```typescript:vim ./contexts/account/infrastructure/persistence/account_repository_impl_sqlite.ts
export class AccountRepositoryImplSqlite implements AccountRepository {
  //実装してください。
}
if (import.meta.main) {
    // deno run --importmap import_map.json --allow-all ./contexts/account/infrastructure/persistence/account_repository_impl_sqlite.ts
    (async ()=> {
            const user = await repo.getByUserPassword("yamamoto", "yamapass");
            log.debug(user);
            if (user === false) {
                log.debug("ユーザー見つからない");
            } else {
                log.debug("ユーザー見つかった");
                user.id = 1;
                log.debug("この上でエラーになる");
            }
    })();
}
```
</details>

正しく実装できれば以下のようなログが出力されます
```bash
$ deno run --importmap import_map.json --allow-all ./contexts/account/infrastructure/persistence/account_repository_impl_sqlite.ts
DEBUG {"id":1,"user":"yamamoto","password":"yamapass","lockNo":null,"updater":null,"updated":"2022-12-01 00:25:10"}
DEBUG ユーザー見つかった
error: Uncaught (in promise) TypeError: Cannot assign to read only property 'id' of object '#<Object>'
                user.id = 1;
                       ^
    at file:///home/yamamoto/src/deno1_28_1-oak/contexts/account/infrastructure/persistence/account_repository_impl_sqlite.ts:169:24
```

#### application layer
<details><summary>インプット</summary>

```typescript:cat ./contexts/account/application/use_case/authorization/input.ts
import { User, Password } from "../../../domain/entity/account.ts";

export type Input = {
    user: User,
    password: Password
};

export function input(params: any): Input {
    return { user: params.user_id, password: params.password};
}
```
</details>

<details><summary>インタラクター</summary>

```typescript:cat ./contexts/account/application/use_case/authorization/interactor.ts
import * as log from "log/mod.ts";
//import { Data } from "../../../domain/entity/account.ts";
import { Authentication, Data as Token } from "../../../domain/entity/authentication.ts";
import { AccountRepository } from "../../../domain/persistence/account_repository.ts";
import { Input } from "./input.ts";

export async function execute<T extends AccountRepository>(input: Input, accountRepository: T): Promise<Token|false> {
    const authResult = await accountRepository.getByUserPassword(input.user, input.password);
    if ( authResult !== false) {
        return Authentication.create(authResult.user);
    } else {
        return false;
    }
}

import { AccountRepositoryImplSqlite } from "../../../infrastructure/persistence/account_repository_impl_sqlite.ts";
if (import.meta.main) {
    (async()=>{
        //成功
        const input:Input = {
            user: "yamamoto",
            password: "yamapass"
        };
        const repo = new AccountRepositoryImplSqlite();
        log.critical( await execute(input, repo));

        //失敗
        const input2:Input = {
            user: "yamamochi",
            password: "yamapass"
        };
        const repo2 = new AccountRepositoryImplSqlite();
        log.critical( await execute(input2, repo2));
    })();
}
```
</details>

動作確認
```bash
$ deno run --importmap import_map.json --allow-all ./contexts/account/application/use_case/authorization/interactor.ts
INFO {"id":1,"user":"yamamoto","password":"yamapass","lock_no":null,"updater":null,"updated":"2022-12-01 00:25:10","created":null}
CRITICAL {"user":"yamamoto","accessToken":"本当は発行する処理が必要","expired":0}
INFO Function getByUserPassword
WARNING Finded rows count 0 or greater than 2
CRITICAL false
```

<details><summary>アウトプット</summary>

```typescript
import { AccessToken, Expired, Data } from "../../../domain/entity/authentication.ts";
export type Output = {
    accessToken: AccessToken
    expired: Expired
};
export function output(output: Data): Output {
    return output;
}
```
</details>

<details><summary>認証コントローラー</summary>

```typescript
import {Status} from "oak/mod.ts";
import * as log from "log/mod.ts";
import { Response, ResponseBody } from "../../../../../common/response.ts";
import { input } from "../../use_case/authorization/input.ts";
import { output } from "../../use_case/authorization/output.ts";
import { execute } from "../../use_case/authorization/interactor.ts";
import { AccountRepositoryImplSqlite } from "../../../infrastructure/persistence/account_repository_impl_sqlite.ts";

export default async function controller(ctx: any) {

    const accountRepository = new AccountRepositoryImplSqlite();

    const result = ctx.request.body(); // content type automatically detected
    if (result.type !== "form") {
        throw new Error ("form以外許可しません");
    }
    const value = await result.value; // an object of parsed JSON
    const user = await value.get('user');
    const password = await value.get('password');

    const inbound = {user, password};

    const authResult = await execute(input(inbound), accountRepository);
    const outbound = (() => {
        if (authResult !== false) {
            return output(authResult);
        } else {
            //ちょっと手抜きで
            return {"res":"error", "reason":"auth error"};
        }
    })();

    const responseBody = ResponseBody.create({
        status: "000",//システムで定義したいであろうコード
        message: "Successed",//Statusを言葉で端的に説明したもの
        data: outbound
    });
    const response = Response.create({
        status: Status.OK,
        type: "json",
        body: responseBody.getData()
    });
    
    ctx.response.status = response.getStatus();
    ctx.response.type = response.getType();
    ctx.response.body = response.getBody();
    return ;
}
```
</details>

ルーティング追加
```typescript
import { Router } from "oak/mod.ts";
import memoListController from "../contexts/memo/application/controller/api/list_controller.ts";
import authorizationController from "../contexts/account/application/controller/api/authorization_controller.ts";

const router = new Router();
router.get("/memo", memoListController);
router.post("/auth/token", authorizationController);

export default router;
```
動作確認
```bash
$ curl -X POST -s -d "user=yamamoto" -d "password=yamapass" http://localhost/auth/token
{"status":"000","message":"Successed","data":{"user":"yamamoto","accessToken":"本当は発行する処理が必要","expired":0}}
```

#### 認可ミドルウェアを作る

エンドポイントとしてではなく、各ルートに挟み込めるミドルウェアが必要になって来ます。

:::note warn

この項目ではミドルウェアの作り方の説明のみとなります。認可の実装自体は説明しません。
:::

```bash
$ mkdir middleware
$ touch ./middleware/authentication.ts
```

```typescript:cat ./middleware/authentication.ts
import { isHttpError, Status } from "oak/mod.ts";

export async function authMiddleware (ctx: any, next: any) {
    try {
    //手抜きで
    const headers = ctx.request.headers;
    const token = await headers.get('Token');
    if (token === "OKToken") {
        await next();
    } else {
        ctx.throw(Status.Unauthorized, "Authentication failed!");
    }

  } catch (err) {
    if (isHttpError(err)) {
      ctx.response.status = err.status;
      ctx.response.type = "json";
      ctx.response.body = {
        status: err.status >= 400 && err.status < 500 ? "fail" : "error",
        message: err.message,
      };
    }
  }
}
```

ルーティングにミドルウェアを追加する
```typescript
import { Router } from "oak/mod.ts";

import { authMiddleware } from "../middleware/authentication.ts";
import memoListController from "../contexts/memo/application/controller/api/list_controller.ts";
import authorizationController from "../contexts/account/application/controller/api/authorization_controller.ts";


const router = new Router();
//router.get("/memo", memoListController);
router.post("/auth/token", authorizationController);

const authedRouter = new Router()
authedRouter.use(authMiddleware);
authedRouter.get("/memo", memoListController);

router.use(authedRouter.routes());

export default router;
```
`/memo`へ先ほど作ったミドルウェアを差し込みました。
手抜きミドルウェアなので、`Token`ヘッダーが`OKToken`の場合に認可を通ったものとして扱っています。

動作確認
```bash:OKTokenの場合
$ curl -H "Token: OKToken" -s http://localhost/memo?user_id=10
{"status":"200","message":"Successed","data":[{"id":28,"userId":10,"content":"memo.content"}]}
```

```bash:OKToken以外の場合
$ curl -H "Token: NGToken" -s http://localhost/memo?user_id=10
{"status":"fail","message":"Authentication failed!"}
```

以上で本記事は終わりですが、これでロクでもない状態のメモ取得APIを改善する事が出来るのでぜひ修正までしてみてください。



<details><summary>tree</summary>
最終的にこのようなツリーになりました。（実装出来なかったファイルも含む）

```bash
.
├── common
│   └── response.ts
├── contexts
│   ├── account
│   │   ├── application
│   │   │   ├── controller
│   │   │   │   ├── api
│   │   │   │   │   ├── authentication_controller.ts
│   │   │   │   │   ├── authorization_controller.ts
│   │   │   │   │   ├── delete_controller.ts
│   │   │   │   │   └── store_controller.ts
│   │   │   │   └── web
│   │   │   │       ├── authentication_controller.ts
│   │   │   │       ├── authorization_controller.ts
│   │   │   │       ├── delete_controller.ts
│   │   │   │       └── store_controller.ts
│   │   │   └── use_case
│   │   │       ├── authentication
│   │   │       ├── authorization
│   │   │       │   ├── input.ts
│   │   │       │   ├── interactor.ts
│   │   │       │   └── output.ts
│   │   │       ├── delete
│   │   │       └── store
│   │   ├── domain
│   │   │   ├── entity
│   │   │   │   ├── account.ts
│   │   │   │   └── authentication.ts
│   │   │   ├── exception
│   │   │   └── persistence
│   │   │       └── account_repository.ts
│   │   └── infrastructure
│   │       ├── persistence
│   │       │   └── account_repository_impl_sqlite.ts
│   │       └── presenter
│   └── memo
│       ├── application
│       │   ├── controller
│       │   │   ├── api
│       │   │   │   ├── delete_controller.ts
│       │   │   │   ├── list_controller.ts
│       │   │   │   ├── store_controller.ts
│       │   │   │   └── update_controller.ts
│       │   │   └── web
│       │   │       ├── delete_controller.ts
│       │   │       ├── list_controller.ts
│       │   │       ├── store_controller.ts
│       │   │       └── update_controller.ts
│       │   └── use_case
│       │       ├── delete
│       │       ├── list
│       │       │   ├── input.ts
│       │       │   ├── interactor.ts
│       │       │   └── output.ts
│       │       ├── store
│       │       │   ├── input.ts
│       │       │   ├── interactor.ts
│       │       │   └── output.ts
│       │       └── update
│       ├── domain
│       │   ├── entity
│       │   │   └── memo.ts
│       │   ├── exception
│       │   └── persistence
│       │       └── memo_repository.ts
│       └── infrastructure
│           ├── persistence
│           │   └── memo_repository_impl_sqlite.ts
│           └── presenter
│               ├── html.ts
│               └── json.ts
├── db
│   ├── migrations
│   │   ├── 20221127111118_create_memos.ts
│   │   └── 20221130235847_create_accounts.ts
│   └── seeds
├── import_map.json
├── log.txt
├── main.ts
├── middleware
│   └── authentication.ts
├── nessie.config.ts
├── routes
│   ├── api.ts
│   └── web.ts
└── sqlite.db
```
</details>

### 

# あとがき
土日祝と平日夜中ほぼ全部潰して書いて何とか一通りの実装手順を書き切る事が出来ました。

実は去年もoakを試しており、その時もクリーンアーキテクチャーを意識して認証周りを作ったので大体の`deno-oak-mysql`での実装手順は分かっていました。しかし今回はDBを`sqlite`に変えて最新バージョンのdenoとoakで作りつつ`Ushio Architecture`やその他クリーンアーキテクチャー関係のサイトを参考にしながらマイグレーションを採用したり多少テストを書いたりと色々試みて改めて作り直した為に時間が掛かりました。

つきましては[開発部長殿](https://qiita.com/kazuyaseo)、次回技術記事を書く機会を頂けるのであればその為の時間の確保を宜しくお願い奉り候。


---

トラストバンクでは一緒に活躍いただけるエンジニアを募集中です。

https://www.wantedly.com/companies/trustbank/projects

[^1]:[Ushio](https://qiita.com/youhei_seasalt)さんの手によって**php、laravelに最適化された**クリーンアーキテクチャーの事。改善・進化し続けている。
特に[型定義](https://github.com/trustbank-inc/nicoca)が面白く、これを使わせる事でLL(軽量プログラミング言語)にしか触れた事の無いphperを強制的にレベルアップさせる事も目論んでいるのではないかと疑っている。
[^2]:OAuth2/JWTは[FastAPI](https://fastapi.tiangolo.com/ja/)を実践してみると理解しやすいと思います。