前回の続き
TypeScript側
db接続部分
/* 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 テーブル定義
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に取りに行く
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部分
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接続部分
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
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
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のほうが楽しいな。まだまだ勉強しないと。