本アーキテクチャを用いて、最小限の CRUD 操作を実装した Web アプリです。
CRUD 操作 | 対象機能 | イメージ |
---|---|---|
C(Create) | 新規投稿 | |
R(Read) | 全投稿参照 | |
U(Update) | 投稿編集 | |
D(Delete) | 投稿削除 |
参考までに...
クライアント 4 層構成でシンプルな CRUD 処理を実装するリファレンスアプリ(Flutter)
アプリケーション層をユースケース層にしたり、プレゼンテーション層からの依存関係をユースケース層のみにしてドメイン層への依存を排除したりしたため図の見直しが必要
お時間のある時に、ソースコードレビューをお願いします。
アーキテクチャの観点からご指摘を頂けると嬉しいです。
ソースの書き方についてもアドバイス頂けたら嬉しいです。
また、アプリケーション層にはどのような役割を持たせたらいいのでしょうか?
僭越ながらご指名頂きましたのでレビューさせて頂きました!
指摘をすべて洗い出しているわけではありませんが、主にアーキテクチャに関する点についてレビュー致しました!
PostRepository
は FirebaseFirestore.instance
に依存しているので、FirebaseFirestore.instance
を Riverpod の DI で外部から注入してあげましょう。PostRepository
のテストコードを書いてみると、そのメリットを実感できると思います。
以下のようにすると、firebaseFirestoreProvider
の中身をモックに差し替えることでテストが可能になります。
また、外から PostRepository
を使えるようにするために、firebasePostRepositoryProvider
も用意しておきます。
final firebaseFirestoreProvider = Provider(
(_) => FirebaseFirestore.instance,
);
// PostRepository(実体) ではなく IPostRepository (インターフェース)を返してあげる
final firebasePostRepositoryProvider = Provider<IPostRepository>(
(ref) => PostRepository(
// FirebaseFirestoreインスタンスを注入する
store: ref.watch(firebaseFirestoreProvider),
),
);
class PostRepository implements IPostRepository {
PostRepository({
required this.store,
});
final FirebaseFirestore store;
///postsコレクション
@override
get postsCol async => store.collection('posts');
プレゼンテーション層が IPostRepository
を使うときは、ref.watch(firebasePostRepositoryProvider)
とすることで IPostRepository
を取得できます。しかし、これでは、プレゼンテーション層がデータ層に依存してしまいます。プレゼンテーション層はドメイン層とアプリケーション層のみに依存するべきです。この問題は Riverpod の overrides を使って以下のように回避できます。
まず、ドメイン層のリポジトリに次のように空の IPostRepository
を返すプロバイダーを定義します。
final postRepositoryProvider = Provider<IPostRepository>(
(_) => throw UnimplementedError(),
);
そして、main()
で、次のように上書きしてあげます。
void main() async {
runApp(
ProviderScope(
overrides: [
postRepositoryProvider
.overrideWithProvider(firebasePostRepositoryProvider),
],
child: const MyApp(),
),
);
}
こうすることで、プレゼンテーション層が IPostRepository
を使うときは、ドメイン層の postRepositoryProvider
を使って IPostRepository
を取得できるようになり、データ層への依存が無くなります。
PostPageViewModel
は IPostRepository
に依存しているので、コンストラクタで外部から受け取るこのコードは良いと思います!外から PostPageViewModel
を使えるようにするために、postPageViewModelProvider
を用意してあげましょう。その際、postRepositoryProvider
を使って IPostRepository
を取得して注入してあげます。
final postPageViewModelProvider = Provider(
(ref) => PostPageViewModel(
ref.watch(postRepositoryProvider),
),
);
PostPage
内で PostPageViewModel
を使いたいときは、次のようにダイレクトに取得できるようになります。かなりすっきり書けますね!ViewModelをメンバ変数に保持する必要も無いので Stateless の ConsumerWidget
でいけます。
class PostPage extends ConsumerWidget {
const PostPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final vm = ref.watch(postPageViewModelProvider);
return Scaffold(
PostPage
はコンストラクタに何も受け取らないので、以下のように簡素に書けます。
Future<void> onPost(BuildContext context) async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const PostPage(),
),
);
}
JsonKey
があることから、データ層のリポジトリでFirestoreから受け取ったJSONデータを直接ドメイン層のエンティティ(このGitHubリポジトリ上ではモデルと呼ばれている)に変換する意図を感じます。ドメイン層で定義するエンティティは、どの層にも依存しないのがよいので(データ層都合で変更がおきたときに、ドメイン層まで変更が影響してしまう)、直接変換するのではなく、一度データ層用のモデルを挟むのがよいと思います。今回のアプリは小さいのでこれで問題なく動くと思いますが。。
こんなイメージです。
データ層のリポジトリで、Firestoreから受け取ったデータ(Map)を、データ層用のクラス(PostDocument)にfreezedのfromJsonを使って変換する。次に、PostDocumentをドメイン層のエンティティであるPostクラスに変換する。データ層のリポジトリはPostクラスを返す。
同じ事を私は上記でやってます。data(Map)をjsonObject(データ層用のクラス)に変換し、jsonObjectをSearchReposResult(ドメイン層のエンティティ)に変換して返しています。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.