RustでMVC & クリーンアーキテクチャっぽく書いてみた
こんにちは。のんです。
久しぶりにRustの記事更新です。
何もしていなかったわけではなく、ブログにできるレベルの成果物ができていなかっただけですw。
成果物
※masterブランチで作業しているので、既に色々更新されている可能性があることにご注意ください。
個人的悩みポイント
mod.rs
について
Rustはmoduleの考え方あので、modを定義する必要があるのですが、クリーンアーキテクチャを適用するとどうしてもファイル数が多くなります。
その際、 mod.rs
というファイルをたくさん作ることになるのですが、これをどうにかスマートに書く方法がないものかなやんでしまいました。
結局、 mod.rs
を作りまくるこで対応しましたが、もう少しいい感じに書く方法はありそう。。
今後に期待。
エラーについて
Rustをやるにあたって一番最初に躓くところだそうです。
特に私はPHPerなので、例外ガンガン書くタイプのプログラマーでした。
Golangなども普段業務で触っているわけではないので、例外がないプログラミング言語をどのように書けばいいのかめっちゃ困りました。
DBアクセスができなかった...とか、データが存在しないとか、ランタイム系のエラーはWEBアプリには付き物なので、通る前提でコーディングできないのが辛いところです。
例外を投げるというより、エラー型?( Result
型)を返すという方がニュアンスとして合っていそうなのでそのように実装していますが、エラーのバケツリレーが始まった感出ていてありゃ〜という感じ
この問題もどうにかしてスマートに書く方法はありそう。
現在は泥臭くエラー返しています。。。がこの方法も悪くはないのでは?とブログ書きながら考えていたりもします。
PostRepository
の実装部分であるこことか
fn find_by_id(id: PostIdentifier) -> Result<Post, String> {
let connection = establish_connection();
let post = match posts_schema::dsl::posts
.find(id.to_string())
.first::<PostModel>(&connection) {
Ok(ret) => ret,
Err(_err) => return Err("Post not found".to_string())
};
return Ok(Post::new(
PostIdentifier { identifier: Ulid::from_string(&post.id).unwrap() },
PostTitle::new(post.title).unwrap()
))
}
それを呼び出すユースケース、こことかの話。
impl UpdatePost {
pub fn process(input: UpdatePostInput) -> Result<(), String> {
let mut post = match PostMySqlRepository::find_by_id(input.identifier) {
Ok(post) => post,
Err(err) => return Err(err)
};
post.change_title(input.title);
PostMySqlRepository::save(post);
return Ok(());
}
}
ぜーんぶResult返すようになっちゃいました。
ここまででRustいいなって思ったところ
強力な安全性
これはRustに限ったことではありませんが、やっぱり強力な型安全、処理安全なところですね。
ValueObject
は採用していますが、これ書く意味ある?(いや、当然書く意味はあるのですが。)と感じてしまうくらい、Rustの仕様を信じてコーディングできるのはすごいなと思いました。
あと、エラー周りも最初は戸惑いましたが、エラーを投げるということすらも 「これくらいのエラーは想定しておけよ」 と言われているようで、 ストイックな言語である ということがヒシヒシと伝わってきます。私は好感持てました。
CQRSでReadModelを実装したときはほぼほぼRustの仕様に乗っからせてもらっています。
#[derive(Serialize)]
pub struct PostReadModel {
identifier: String,
title: String
}
そのほかの実装部分
pub struct GetPostMySqlQuery {}
pub struct GetPostMockQuery {}
pub trait GetPostQuery {
fn get_post() -> Vec<PostReadModel>;
}
impl GetPostQuery for GetPostMySqlQuery {
fn get_post() -> Vec<PostReadModel> {
let connection = establish_connection();
let results = posts_schema::dsl::posts
.load::<Post>(&connection)
.expect("Error loading posts");
let mut posts = Vec::new();
for result in results {
posts.push(PostReadModel {
identifier: result.id,
title: result.title
})
}
return posts;
}
}
impl GetPostQuery for GetPostMockQuery {
fn get_post() -> Vec<PostReadModel> {
return Vec::from([PostReadModel {
identifier: Ulid::new().to_string(),
title: "mock".to_string()
}]);
}
}
serde = { version = "1.0", features = ["derive"] }
ただの構造体をReadModelとして定義して、jsonにシリアライズできるようにしています。
これだけストイックなら、コードを短く書こうと書いている側も頑張るはず
これは個人の感想ですが、ここまでストイックな言語ですと、書けば書くほどエラーに直面します。
冗長なコードがだらだら書くとエラーに当たりやすくなるので、短く簡潔に必要なものだけを書くように方向性を矯正させられるとも考えました。
とりあえず動けば良かろうをすると、大変な目に合いますねw
最後に
とはいえ、まだまだ基礎中の基礎しか実装てきていないので、このリポジトリで色々勉強していきたいと思います。
そのほかにUlid
のライブラリを導入したりしていましたが、この辺は書くまでもないことなので割愛しています。
多分ここまでくればシンプルなWEBアプリは書けるようになっているはずですが、メールの送信とか、 curl
の実行くらいは勉強しておきたい。
「rocket使え」ってのはなしでw
FW使うと勉強の意味無くなってしまいますから。
また進捗あれば記事にします。
そのときはよしなに。
.