技術な日々

気になったこと技術の話

今更ながらWeb API The Good Partsを読んでみた

今更ながら読んでみたシリーズ第八弾

経緯

業務でREST APIを開発することがあるんだが、正しさみたいなものを理解せずに開発していたりしていた。なので、REST APIで開発するとき、指針じゃないが、こう作れば正しい、というのを理解したかったので、読んでみました。
これからは、GraphQLばっかりになっていくと思いますが、REST APIで開発したものってたくさんあると思うので、知ってて損はないかなと思います。

Web API: The Good Parts | 水野 貴明 |本 | 通販 | Amazon

読んだほうがいい人

  • なんとなくREST APIを開発しているエンジニア

感想

まず250ページぐらいなので、厚さとしては薄いので、ライトな感じで読めるかなと思います
内容としては、URL設計からセキュリティまでAPIを開発、公開するにあたって、気をつける点などがあって、とても参考になりました。
読んでて思ったのが、APIデファクトスタンダードみたいなものってなくて、GAFAじゃないけど、よく使われているAPIを参考にするといいよっていうのがびっくりした感じですね。世の中に公開されているAPIを参考にしつつ、自分のプロジェクトなどを取り入れるといいのかーってことがわかっただけでも読んだ価値はありそうでした。

まだまだREST APIで開発したものってたくさん残っているだろうし、作ってるところもあるだろうし、読んで損はしなかったです。地味に嬉しかったのは、ページ最後にチェックリストがあったのが嬉しかったですね

まとめ

REST API開発をしているなら読んでおくべき一冊でした。
知ってることも多少はありましたが、だいたいは参考になったので、開発するときは本とにらめっこしながら、開発したいと思います。

今更ながらClean Codeを読んでみた

今更ながら読んでみたシリーズ第七弾

経緯

Clean Architectureを読んでから、ずっとClean Codeが気になっていた。Clean Codeを読んで、プログラムの書き方が変わった、ということも聞いたので、どういうものか読んでみることにしました

https://amzn.asia/d/iv3PQKw

読んだほうがいい人

  • リーダブルコードを読み終わって、中級ぐらいに差し掛かりそうなエンジニア

感想

全体的な感想としては、GoodCode、BadCodeを先に読んでいたりしたので、似たような内容だったかもな、という感じです
こちらが先で読んだ本が後に発売されたものもあると思いますが
個人的感想としては、14〜17章にかけては、あまり学びが少ないというか、Javaのコードを例にリファクタリング等をしていく説明になるので、Javaがそもそも理解できてないと、ちょっとつらいですね。
Javaエンジニアであれば、読んでて面白いのかなと思いました

なので、1〜13章あたりまでがClean Codeとは、の説明になるかなと思いました。13章は並行性なので、活用場面はあまり多くなさそうかなとは思いますが

とりあえず印象に残ったところだけ感想を別途書きたいと思います。

コメント書くな

4章で書かれますね。いかにコメント書くとコードが読みにくくなるか、というような内容です。
Clean Code読むとコメント書かなくなりますね。まず。ただ、なぜコメント書くなって言ってるかというと、適切な変数名、関数名、クラス名があれば、コメント必要ないだろってことなんです。たしかにコードを読めば何をしているか理解できるので、コメントはいらないですね。
でも、適切な変数名、関数名、クラス名になってるコードは、コメントはいらないってことなので、そうじゃないコードはコメントがないと余計に読みにくくなるコードになっている可能性はあると思いますね。そうならないように何度も繰り返し考えて名前付けしなくちゃいけないんだなとも思います。

DI、TDD

章のタイトルになってませんが、紹介されてます。結局、ここに落ち着くというか大切というかって感じでした。特にTDDに関して、大きく紹介してるわけではありません。むしろリファクタリングのほうを14章以降で紹介している感じがしました。
ここでTDDについて、しっかり解説していないので、clean craftsmanshipで解説しているのかなと思いました。
結局、DIで疎結合にし、TDDでリファクタリングしてもバグのないコードを作ることが大切ってことなんだなと思いました

まとめ

ぶっちゃけ13章まで読めば十分かなと思いました。Javaエンジニアの人は14章以降四でも面白いと思いました
繰り返しになりますが、結局、適切な名前、抽象化、DI、TDD、リファクタリングが大切なんだなということが改めて理解できた感じです。
GoodCode、BadCodeのほうが、読みやすかったので、わかりにくいなと思ったら、こっちを読んでみると良いかもです。

今更ながらSQLアンチパターンを読んでみた

今更ながら読んでみたシリーズ第六弾

経緯

TDD→リファクタリング単体テストに関連した本を読み、そろそろDB側の本を読みたいなと思っていたところ、ラジオでSQLアンチパターンの本を聞いて、読みたくなったので、読んでみました。
あとは、テーブル設計の本を読んだことがなく、アンチパターンってどんなものがあるのかなど気になったのもあります。

https://amzn.asia/d/9DATCW5

読んだほうがいい人

感想

各章はそれぞれのアンチパターンについて、解説されていて、わかりやすいです。
気になった、印象に残った等のアンチパターン

に分けて、簡単に記載したいと思います。

これアンチパターンだったのか!編

何でも主キーの列名をidにするな!

これはDB設計のアンチパターンになります。
今も昔もテーブルの主キーはidになってるテーブルをいくつも見ました。
本書でも記載されてますが、フレームワークのORM仕様でテーブルの主キーはid、と決まっていたりしたので、特に疑問を抱くことがなかったんですが、言われてみるとたしかにアンチパターンかも・・
主キーの列名はidじゃなくて、usersテーブルであれば、user_idなどにしましょうってことだそうです

このアンチパターン見たことあるぞ!編

データに区切り文字(カンマなど)で区切ってデータ登録する

1,2,3,4,5見たい感じでデータ登録するやつですね。これはどこかで見たことあるな、と思いました
言われなくてもやっちゃだめなやつ
中間テーブルを作って、多対多のテーブル構造にしましょうってことだそうです

そのアンチパターンやってしまってたぞ!編(恥部をさらす)

外部キーの値をメタデータで登録する

これは説明がちょっと下手かもしれませんが、aテーブルと結合したいときはtypeカラムのデータはa、bテーブルと結合したいときは、typeカラムのデータはbのように文字列型で格納し、親テーブルと結合するときは、a.type = 'a'のようなSQLを組んでしまうことになります。
読んだとき、これやったことがあるな・・と思いました。
まさにアンチパターンしてました・・。
レビューOKだったのですが、レビューした開発者全員もこれがアンチパターンだと、知らなかった、ということになりますな
これも中間テーブルを作成して、列のデータで結合テーブルを決めるようなことはしないようにってことでした
※詳しくは本参照

まとめ

とても参考になったな、と思うのと、常に机の横においておきたい一冊になりました。
テーブル設計するときは、これを見てアンチパターンの設計になっていないか、
どう設計したら良いかなと迷ったときに参考になる一冊でした

Rustでユーザー登録時にパスワードをhash化登録と認証

Rustでパスワードをハッシュ化して、登録し、認証するにはどうすればいいか気になったので、実装してみました。
axumで作ったアプリがあるので、それに実装してます。

概要

単純にユーザ作成時にリクエストされたパスワードをハッシュ化して、DBに登録
例えばIDとパスワードがリクエストされたとき、登録されたパスワードとリクエストパスワードを確認して、認証
といった内容です。

調べたところ、パスワードのハッシュ化はargon2を使うのが良さそうだったので、こちらのクレートで実装してみました。

docs.rs

ハッシュ化

コードは以下のようにしてます。
argon2クレートのDocにあるUsageとほぼ同じです。
簡単に解説すると、

  • salt作成
  • argo2を使って、パスワードをハッシュ化

してます。

let salt = SaltString::generate(&mut OsRng);
let argo2 = Argon2::default();
let password_hash = argo2.hash_password(password.as_bytes(), &salt).unwrap().to_string();

OsRngについて

salt生成時にしているこれについて、わからなかったので、調べてみました。
OsRngは、OSの乱数生成器を使用して、暗号論的に安全な乱数を生成するときにしていするものみたいで、予測不可能な乱数になるので、セキュリティ的にはこれを指定するのがいいっぽいみたいです。
rand::thread_rng()をしている方法もありますが、こちらはOsRngよりも生成が高速ですが、セキュリティでいえばOsRngのほうが安全性はいいみたいです。
セキュリティ重視はOsRng、パフォーマンス重視はrand::thread_rng()といったところでしょうか。
どちらにしても大きな差分はないと思うので、好みなのかな?と思います。

Argon2のインスタンスについて

デフォルト値を使ってますが、下記のようにカスタムすることも可能です

Argon2::new(
        Algorithm::Argon2id,
        Version::V0x13,
        Params::new(15000, 2, 1, None).unwrap(),
    )

認証

コードは以下のようにしてます。
※エラー処理等に関しては、アプリの適当な仕様になってます。

処理としては、

  1. ハッシュ化したDB保存パスワードをパース 2.リクエストパスワードとパースしたパスワードを検証
  2. 結果Okならtrue, Errならエラーメッセージ

という流れ

let pared_hash = PasswordHash::new(&db_password).map_err(|_| anyhow::Error::msg("Password parse error"))?;
Argon2::default().verify_password(request_password.as_bytes(), &pared_hash)
.map(|_| true)
.map_err(|_| anyhow::Error::msg("Password verification failed"))

まとめ

argon2は簡単にパスワードハッシュ化、検証できるので、とても便利でした
一つ迷ったのは、saltで、これはDoc通りの指定が一番良さそうかなと思います。

今更ながらGood Code, Bad Codeを読んでみた

今更ながら読んでみたシリーズ第五弾

経緯

Googleのテックリードが徹底解説!に惹かれて読みたくなった。後はユニットテストについても、書かれているので、そこも気になった。
また、対象は1〜3年目のエンジニアだが、とはいえば学びがあると思ったのも理由

読んだほうがいい人

  • 1〜3年目のエンジニア
  • 3年目のエンジニア以上の経験者でも読んだことがない人
  • この1冊でまるっと学びたいエンジニア

感想

内容は、

で別れている
Part2, 3は実際に悪いコードの説明の後に良いコードの解決がある
という流れだった。
それぞれのPartごとに思ったことなど簡単に記載したいと思う。

Part1 理論編

タイトル通り、理論の説明で、これは後のPart2でなぜ良いコードなのかにつながる話になっている
個人的には少しわかりにくい内容だったと思うが、言われていることはしっくりきた。
エラーについては、語られることが少ないと思っているので、ここは学びがあったかなと思う
また、少しづつ読んでいったせいもあるかもしれないが、Part2以降でPart1で説明したこの部分、と言われても正直、どれだっけ?となってしまったので、先にPart2から読んでもいいと思った。

Part2 実践編

実際にコードで説明してくれていてとてもよかったが、前置きの説明がわかりずらいものも正直あったかなと。さらにコードで説明するので、このコードはどういうシステムのコードなのか、という説明もあり、より想像力も必要かなと思う。
後はJavaなので、Java知らないと少しついていけないかも?とは思った。(知っていればより理解が深まるはず)

Part3 ユニットテスト

正直に言うと、「単体テストの考え方/使い方」本の凝縮された内容、という印象を受けた
なので、「単体テストの考え方/使い方」本がユニットテストについて、深く理解ができるので、詳しく知りたい人はこちらを読むべきで、要点などコンパクトに知りたい人については、全然ありじゃないかと思った。
自分は「単体テストの考え方/使い方」本をすでに読んでいるので、これ「単体テストの考え方/使い方」で言ってたやつだ、と思った

まとめ

内容は他の本で言われていることが多かったかなと思った(抽象化レイヤーとか)
エラー処理については、あまりなかったと思うので、そこは学びでした
包括的に良いコード、良いユニットテストを書くにはどうするか?という課題を持った人であれば、この1冊は買う勝ちがあるかなと思います。 ※1〜3年目のエンジニアでなくても、学びはなるので、色んな人のためになる本かな

Axumでファイルダウンロード処理を実装する

Axumが大好きってわけじゃないが、資料がたくさんあり、開発しやすいので、Axumでいろいろ実装してますが、
AxumというかRustでファイルダウンロード、どうやるんだっけ?という疑問になり、Axumにファイルダウンロード機能を実装してみた

概要

簡単に機能だけざっくり説明すると、DBから取得したデータをJsonに変換し、jsonファイルとしてダウンロードさせる
というAPI

実装

流れとコードは下記

  1. DBから取得したデータをres変数(戻り値はResult<Option<Vec<構造体>>>)に格納
  2. レスポンス用の構造体(Vec)に変換
  3. レスポンス用の構造体をバイトベクターシリアライズ
  4. Cursorにラップし、AsyncRead扱いにする
  5. ReadStreamに変換し、非同期のストリームにする
  6. レスポンスボディに設定
  7. ヘッダーにcontent-typeとdispositionを設定
  8. ヘッダーとボディをレスポンス
use tokio_util::io::ReaderStream;

pub async fn download_histories(
    Extension(modules): Extension<Arc<Modules>>,
    Path(id): Path<String>,
) -> Result<impl IntoResponse, StatusCode> {
    let res = modules.bank_manager_use_case().download_histories(id).await;

    match res {
        Ok(dl_histories) => dl_histories
            .map(|data| {
                let json: Vec<JsonHistoriesDownload> = data.into_iter().map(|d| d.into()).collect();
                let json_data = serde_json::to_vec(&json).expect("error");
                let cursor = Cursor::new(json_data);

                let stream = ReaderStream::new(cursor);
                let body = Body::from_stream(stream);

                let mut headers = HeaderMap::new();
                headers.insert(header::CONTENT_TYPE, HeaderValue::from_static("application/json"));
                headers.insert(
                    header::CONTENT_DISPOSITION,
                    HeaderValue::from_static("attachment; filename=\"deposit_histories.json\""),
                );

                (headers, body).into_response()
            })
            .ok_or_else(|| StatusCode::NOT_FOUND),
        Err(_) => {
            Err(StatusCode::INTERNAL_SERVER_ERROR)
        }

感想

ストリームにするのに、どのクレート使えばいいのかがわからず、苦戦しました。調べるとtokio-utilをどうやら使えばいいみたい、というのがわかったが、次にじゃあ、DBから取得したデータをどうやってストリームに読み込ませばいいのか?で、また苦戦
バイト列にする必要があり、それにはCursorを使えばいいらしい、というのがわかって、やっとファイルをダウンロードすることができました。
もうちょっと苦戦するかなと思いましたが、思ったより早く実装できました。コードだけ見れば、難しいことはしてませんが、まだまだRust力が足りないなと思います。
ファイルダウンロード実装したいときは、ご参考までに!

RustでGraphQL

GraphQLをさわったとことがなかったので、Rustで実装してみることにしました。
RustでGraphQLサーバを立てる場合、調べた感じ代表的なクレートとしては、juniperかasync-graphqlのどちらかっぽい。
axumを使ったexampleがどちらもあったので、試したところ、async-graphqlのほうがシンプルで使いやすかったので、こちらで実装してみる

github.com

github.com

概要

アプリっぽい感じで使ってみたかったので、適当に銀行口座を作るようなAPIを元にGraphQLサーバを立ててみた
クリーンアーキテクチャにして、REST APIとの両立やDynamoDBを使いたかったので、結局、いろいろやってるAPIになりました。

github.com

環境

GraphQL

実装としてはRouterとSchemaとでファイルを分けました。今回は、検索と登録だけなので、QueryとMutationだけになってます。(ちなみにGraphQLもasync-graphqlもほぼ知識がない状態なので、あしからず)

    let schema = Schema::build(QueryRoot, MutationRoot, EmptySubscription)
        .data(modules.clone())
        .finish();

    let app = Router::new()
        .route("/", get(graphql_playground).post(graphql_handler))
        .layer(Extension(schema))
        .fallback(not_found_handler);

async-graphqlのbookやasync-graphqlを実装したzennの記事を参考にして、実装できました。
感想としては、GraphQLの知識がほぼゼロでもなんとかなったなという感じです

続いて肝心のSchemaは、resolverファイルを作ってこっちに定義しました。
unwrap()使ってたりとエラー処理に関してはかなり雑ですが、目的はGraphQLを使ってみる、なので、そこはご愛嬌。。
クリーンアーキテクチャ+DIしているので、依存関係はModules構造体に定義していて、それを先程のdata()関数に渡して、各関数で、Contextから取得できるようにしてます。

#[Object]
impl QueryRoot {
    async fn ping(&self, ctx: &Context<'_>) -> Ping {
        ctx.data::<Arc<Modules>>().unwrap().bank_query_use_case().ping().await
    }

    async fn find(&self, ctx: &Context<'_>, id: String) -> BankQueryAccount {
        let res = ctx.data::<Arc<Modules>>()
            .unwrap()
            .bank_query_use_case()
            .view_account(id)
            .await;

        match res {
            Ok(account) => account.unwrap(),
            Err(e) => panic!("error: {}", e),
        }
    }
}

#[Object]
impl MutationRoot {
    async fn create(&self, ctx: &Context<'_>, create_account: CreateAccount) -> BankQueryAccount {
        let res = ctx.data::<Arc<Modules>>()
            .unwrap()
            .bank_query_use_case()
            .add_account(create_account.into())
            .await;

        match res {
            Ok(new_account) => new_account.unwrap(),
            Err(e) => panic!("error: {}", e),
        }
    }
}

リクエストされたSchemaやレスポンスの定義ですが、これは、別ファイルの構造体でそれぞれ定義してます。
SimpleObjectのアトリビュートがあるのが、Query
InputObjectのアトリビュートがあるのが、Mutationです。

#[derive(SimpleObject)]
pub struct Ping {
    pub status: String,
    pub code: i32,
}

#[derive(SimpleObject)]
pub struct BankQueryAccount {
    pub bank_id: String,
    pub branch_office_id: String,
    pub name: String,
    pub money: i32,
}


#[derive(InputObject)]
pub struct CreateAccount {
    pub bank_id: String,
    pub branch_office_id: String,
    pub name: String,
    pub money: i32,
}

実際にplaygroundでリクエストするときのがクエリこれです

{
  find(id: "1") {
    bankId,
    branchOfficeId,
    name,
    money
  }
}

mutation {
  create(createAccount: { bankId: "test", branchOfficeId: "test", name: "graphql", money: 100 }) {
    bankId
    branchOfficeId
    name
    money
  }
}

感想

クリーンアーキテクチャREST APIも実装したあとにGraphQLも実装したので、ごちゃごちゃした感じになってしまいましたが、こうやって使うんだな、というのはよくわかりました。
やることたくさんあって、わからん!というわけではなく、GraphQLがある程度、理解できたら、実装にはそんな苦労しないのかな?という印象。
実装観点でREST APIとの違いでいえば、Schemaを定義し、RouterにSchemaをビルドしたものを渡す部分が異なるだけでそれ以外はやることは同じなので、両立もできそうだし、REST APIから移行もできそう、とは思いました。

まとめ

async-graphqlを使えば、GraphQLの実装はそんなに難しくないよ
後は仕事で導入するときは、以下の本を読んでメリデメを理解してからのほうがよいなと思いました。

https://www.amazon.co.jp/%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AEGraphQL-%E2%80%95Web%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%A6%E5%AD%A6%E3%81%B6%E6%96%B0%E4%B8%96%E4%BB%A3API-Eve-Porcello/dp/487311893X