TypeScriptとFsharpでMySQLにアクセスしてみた

前回の続き

## TypeScript側


### db接続部分
```TypeScript:db.ts
/* dbにknexで接続する */

import knex from 'knex';

// knexConfig
const knexConfig = {
  client: 'mysql2',
  connection: {
    host: '127.0.0.1',
    port: 3306,
    user: 'root',
    password: 'root',
    database: 'mydb'
  }
};

export const db = knex(knexConfig);
```
なんの変哲もないknexですな。
prismaとかkyselyとか考えたけど簡単なknexで。


## type テーブル定義
```TypeScript:types.ts
import { z } from 'zod';

export const UserTableSchema = z.object({
  ID: z.number(),
  NAME: z.string(),
  AGE: z.number(),
  EMAIL: z.string()
});

export type User = z.infer<typeof UserTableSchema>;
```
べつにzodにしなくても良いがお洒落にzodにしてみた

### users.tsで使う関数。knexでDBに取りに行く

```TypeScript:usersFunction.ts
import { isEmpty } from 'fp-ts/lib/Array';
import { Either, left, right } from 'fp-ts/lib/Either';
import { db } from '../../../../db/db';
import { User } from '../../../../types/type';

/* dbからusersを全部取得する関数 */
export const getUsers = async (): Promise<Either<string, User[]>> => {
  // dbからusersを全部取得する
  const users = await db<User>('USER').select('*');
  if (isEmpty(users)) return left("users don't exist");
  // usersを返す
  return right(users);
};

/* dbからusersをidで取得する関数 */

export const getUserById = async (id: number): Promise<Either<string, User>> => {
  // dbからusersをidで取得する
  const [user] = await db<User>('USER').select('*').where('ID', id);
  if (user === undefined) return left("user doesn't exist");
  return right(user);
};
```
fp-ts使うまでもないが一応使ってみた。
ごくごく普通の関数ですな。

### usersrouter部分

```TypeScript:users.ts
import { Router } from 'express';
import { Either, isLeft, left, right } from 'fp-ts/lib/Either';
import { User } from '../../../types/type';
import { getUserById, getUsers } from './functions/usersFunctionts';

export const usersRouter = Router();

type UsersSuccess = {
  type: 'SUCCESS';
  users: User[];
};
type APIError = {
  type: 'ERROR';
  message: string;
};

usersRouter.get('/', async (req, res) => {
  const usersResult = await getUsers();
  const result: UsersSuccess | APIError = isLeft(usersResult)
    ? { type: 'ERROR', message: usersResult.left }
    : { type: 'SUCCESS', users: usersResult.right };
  res.send(result);
});

/* safeParseIntをfp-tsのEither型で作成する */
const safeParseInt = (s: string): Either<string, number> => {
  const n = parseInt(s, 10);
  return Number.isNaN(n) ? left('id is not number') : right(n);
};
/* idをgetしたとき、そのidに合致したJSONが返る */
usersRouter.get('/:id', async (req, res) => {
  const eitherId = safeParseInt(req.params.id);
  if (isLeft(eitherId)) {
    res.send({ type: 'ERROR', message: eitherId.left });
    return;
  }
  const user = await getUserById(eitherId.right);
  if (isLeft(user)) {
    res.send({ type: 'ERROR', message: user.left });
    return;
  }
  res.send({ type: 'SUCCESS', user: user.right });
});

```
最後のほう、もうちょっとpipeできれいにまとめて出力できんもんか。できそうだがchain時にpromiseとかあるとうまくできん。まだまだ修練が足りませんな。

そんな感じでtsは実装終了。

次にFsharp

## Fsharp

### DB接続部分

```Fsharp:db.ts
module DB

open Dapper.FSharp.MySQL
open System.Data
open MySql.Data.MySqlClient

Dapper.FSharp.MySQL.OptionTypes.register ()



(* mysqlのIDbConnection *)
let db: IDbConnection =
    new MySqlConnection("server=localhost;user=root;database=mydb;port=3306;password=root;")
```
接続部分というか接続準備部分というか……。

### usersFunction


```Fsharp:usersFunction.fs
module usersFunction

open DB
open Dapper.FSharp.MySQL
open Giraffe

type USER =
    { ID: int
      NAME: string
      AGE: int
      EMAIL: string }

let USERTable = table'<USER> "USER"

type ResultSuccess = { ``type``: string; users: USER list }
type ApiError = { ``type``: string; message: string }
type UserResuleSuccess = { ``type``: string; user: USER }

// usersがemptyであればApiErrorのJSONに、そうでなければResultSuccessのJSONに変換する関数
let userResult (users) =
    if users = [] then
        json
            { ``type`` = "ERROR"
              message = "users is empty" }
    else
        json { ``type`` = "SUCCESS"; users = users }


(* dbからusersを全部取得する関数 *)
let getUsers =
    select {
        for p in USERTable do
            selectAll
    }
    |> db.SelectAsync<USER>
    |> Async.AwaitTask
    |> Async.RunSynchronously
    |> Seq.toList

(* dbからuserが発見できなければERROR<そうでなければSUCCESSを返す関数 *)


(* dbからusersをidで取得する関数 *)
let getUserById id =
    select {
        for p in USERTable do
            selectAll
            where (p.ID = id)
    }
    |> db.SelectAsync<USER>
    |> Async.AwaitTask
    |> Async.RunSynchronously
    |> Seq.toList

let getUser id =
    let user = getUserById id

    if user = [] then
        json
            { ``type`` = "ERROR"
              message = "user is not found" }
    else
        json
            { ``type`` = "SUCCESS"
              user = List.head user }

```

なんだか非同期処理が下手くそな気がする。
力技でなんとかしてる気がする。
きれいな実装じゃない気がする。

### usersRouter

```fsharp:users
module usersRouter

open Saturn
open Giraffe
open usersFunction


let usersRouter =
    router {
        get "" (getUsers |> userResult)
        getf "/%i" (fun id -> (getUser id))
    }

```
ここはほとんど変化なしかな。
もし%iじゃなくてstring型がきたときの処理とか書いてないなぁ……。

似たようなことはできるようになったけど、まだまだ関数型っぽくというかFsharpっぽく書けてない気がする。

しかしFsharpは書いてて楽しいし、ビルドしたらいろんな環境で高速に動くのがいいね！
JavaScript大好きだけどバイナリにできへんのがなんとかならんかという感じだ。
nexeとかpkgでできるけどnode.js入れるとファイルサイズ大きすぎやねん。electronもそうやけど。
かといってgoは関数型っぽく書けないし。
rustはちょっと難しいし。

ということでfsharpを勉強しているのだ。TypeScriptとも近い気がするし。Csharpは昔書いてた。まあいい言語だけどFsharpのほうが楽しいな。まだまだ勉強しないと。