干净的架构、用例和实体
Clean Architecture, UseCases and Entities
好的,所以我刚刚开始了一个新的 Android 项目,并想尝试实施 Uncle Bob 的 Clean Architecture。我在使用 RxJava 和 GitHub 示例和样板以及 Fernando Cerjas 的博客(如 this article)中的东西方面有一个很好的开始,但仍然对如何实现一些用例有一些疑问。
TL;DR
一个实体是否应该具有另一个实体的字段(在我的示例中,User
具有 List<Messages>
字段)?
或者 Presenter 应该结合 UseCases 来构建一个映射到多个实体的 ViewModel(那么你如何编写映射器?)?
或者 Presenter 是否应该有一个与每个 UseCase/Entity 关联的 ViewModel,并创建某种 "wait for all data to onNext" 来为每个 ViewModel 调用 view.show()?
基本上,UseCases 应该只 return 实体吗?实体可以由其他实体组成(如 class 的字段)吗?实体只是愚蠢的数据模型 POJO 吗?如何表示 'join SQL' 个查询?
例如,我们以一个简单的 users/messages 应用为例。
我想实现两个视图:UserList
和 UserDetails
:
UserList
显示 Users
的列表
UserDetails
显示用户信息及其最新消息。
UserList
非常简单,我可以看到如何编写关联的用例和层(代码如下)。
我的问题是 UserDetails
屏幕。
如果我希望所有数据同时在视图中传递,我应该如何编写我的 GetUserInfoUseCase
代码(比如构建一个由用户 class 和字段列表组成的 ViewModel )? GetUserInfoUseCase
的 return 值应该是多少?
我应该编写一个 Observable<User> GetUserInfoUseCase
和一个 Observable<List<Message>> GetUserLatestMessages
并在我的演示者中以某种方式合并它们吗?如果是,我该如何管理它,因为我的 Presenter 中没有 Observables(我只传递一个 Observer 作为我的 UseCases 参数)?
用户实体
public abstract class User {
public abstract long id();
public abstract String name();
...
}
消息实体
public abstract class Message {
public abstract long id();
public abstract long senderId();
public abstract String text();
public abstract long timstamp();
...
}
获取用户用例
public class GetUsersUseCase extends UseCaseObservableWithParameter<Boolean, List<User>, UsersRepository> {
@Inject
public GetUsersUseCase(UsersRepository UsersRepository,
@Named("Thread") Scheduler threadScheduler,
@Named("PostExecution") Scheduler postExecutionScheduler) {
super(usersRepository, threadScheduler, postExecutionScheduler);
}
@Override
protected Observable<List<User>> buildObservable(Boolean forceRefresh) {
if(forceRefresh)
repository.invalidateCache();
return repository.getUsers();
}
}
用户演示者
public class UsersPresenter extends BasePresenter<UsersContract.View> implements UsersContract.Presenter {
@Inject
GetUsersUseCase mGetUsersUseCase;
@Inject
UserViewModelMapper mUserMapper;
@Inject
public UsersPresenter() {
}
@Override
public void attachView(UsersContract.View mvpView) {
super.attachView(mvpView);
}
@Override
public void detachView() {
super.detachView();
mGetUsersUseCase.unsubscribe();
}
@Override
public void fetchUsers(boolean forceRefresh) {
getMvpView().showProgress();
mGetUsersUseCase.execute(forceRefresh, new DisposableObserver<List<User>>() {
@Override
public void onNext(List<User> users) {
getMvpView().hideProgress();
getMvpView().showUsers(mUsersMapper.mapUsersToViewModels(users));
}
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
getMvpView().hideProgress();
getMvpView().showErrorMessage(e.getMessage());
}
});
}
}
UseCaseObservableWithParameter
public abstract class UseCaseObservableWithParameter<REQUEST_DATA, RESPONSE_DATA, REPOSITORY> extends UseCase<Observable, REQUEST_DATA, RESPONSE_DATA, REPOSITORY> {
public UseCaseObservableWithParameter(REPOSITORY repository, Scheduler threadScheduler, Scheduler postExecutionScheduler) {
super(repository, threadScheduler, postExecutionScheduler);
}
protected abstract Observable<RESPONSE_DATA> buildObservable(REQUEST_DATA requestData);
public void execute(REQUEST_DATA requestData, DisposableObserver<RESPONSE_DATA> useCaseSubscriber) {
this.disposable.add(
this.buildObservable(requestData)
.subscribeOn(threadScheduler)
.observeOn(postExecutionScheduler)
.subscribeWith(useCaseSubscriber)
);
}
}
用例
public abstract class UseCase<OBSERVABLE, REQUEST_DATA, RESPONSE_DATA, REPOSITORY> {
protected final REPOSITORY repository;
protected final Scheduler threadScheduler;
protected final Scheduler postExecutionScheduler;
protected CompositeDisposable disposable = new CompositeDisposable();
public UseCase(REPOSITORY repository,
@Named("Thread") Scheduler threadScheduler,
@Named("PostExecution") Scheduler postExecutionScheduler) {
Timber.d("UseCase CTOR");
this.repository = repository;
this.threadScheduler = threadScheduler;
this.postExecutionScheduler = postExecutionScheduler;
}
protected abstract OBSERVABLE buildObservable(REQUEST_DATA requestData);
public boolean isUnsubscribed() {
return disposable.size() == 0;
}
public void unsubscribe() {
if (!isUnsubscribed()) {
disposable.clear();
}
}
}
一个问题中有很多问题。让我试着巩固一下我认为我理解的是你的关键问题
实体可以相互引用吗?答案是:是的。也在
干净的架构你可以创建一个实体相互连接的领域模型
UseCase 应该返回什么?
回答:用例定义输入 DTO(数据传输对象)和最适合用例的输出 DTO。鲍勃叔叔在他的书中写道,实体不应传递给用例或从用例中返回
那么主持人的作用是什么?回答:理想情况下,演示者只转换数据。它将对一层最方便的数据转换为对另一层最方便的数据。
希望本指南能帮助您回答您的详细问题
您可以在我最近的帖子中找到更多详细信息和示例:
https://plainionist.github.io/Implementing-Clean-Architecture-UseCases/
和
https://plainionist.github.io/Implementing-Clean-Architecture-Controller-Presenter/
基本上,您想尽可能地推动您的“工具性”感知代码(在圆圈上)。
用例非常接近模型并包含大量业务逻辑 - 您希望这一层非常干净,以便能够快速轻松地进行单元测试。所以,这一层应该对存储一无所知。
但有趣的部分是当 Room 进入房间时 :) Room 使得拥有可以在周围使用的类似模型的对象变得如此容易,如果你使用带注释的 Room,我认为这是一个灰色区域 classes是否适合您的型号。
如果您将 Room 对象视为数据层对象,那么您应该在到达用例之前将它们映射到您的业务对象。
如果您使用 Room 作为 DAO 的内置映射器来建模对象,那么在我看来,您可以在您的用例中使用它们,尽管干净的纯粹主义者可能不会同意这一点。
我的务实建议是 - 如果您的模型具有由多个实体构建的复杂结构,则为其创建一个专用模型 class 并将实体映射到它。
如果您有地址之类的东西,IMO 只需使用房间实体即可。
好的,所以我刚刚开始了一个新的 Android 项目,并想尝试实施 Uncle Bob 的 Clean Architecture。我在使用 RxJava 和 GitHub 示例和样板以及 Fernando Cerjas 的博客(如 this article)中的东西方面有一个很好的开始,但仍然对如何实现一些用例有一些疑问。
TL;DR
一个实体是否应该具有另一个实体的字段(在我的示例中,User
具有 List<Messages>
字段)?
或者 Presenter 应该结合 UseCases 来构建一个映射到多个实体的 ViewModel(那么你如何编写映射器?)?
或者 Presenter 是否应该有一个与每个 UseCase/Entity 关联的 ViewModel,并创建某种 "wait for all data to onNext" 来为每个 ViewModel 调用 view.show()?
基本上,UseCases 应该只 return 实体吗?实体可以由其他实体组成(如 class 的字段)吗?实体只是愚蠢的数据模型 POJO 吗?如何表示 'join SQL' 个查询?
例如,我们以一个简单的 users/messages 应用为例。
我想实现两个视图:UserList
和 UserDetails
:
UserList
显示Users
的列表
UserDetails
显示用户信息及其最新消息。
UserList
非常简单,我可以看到如何编写关联的用例和层(代码如下)。
我的问题是 UserDetails
屏幕。
如果我希望所有数据同时在视图中传递,我应该如何编写我的 GetUserInfoUseCase
代码(比如构建一个由用户 class 和字段列表组成的 ViewModel )? GetUserInfoUseCase
的 return 值应该是多少?
我应该编写一个 Observable<User> GetUserInfoUseCase
和一个 Observable<List<Message>> GetUserLatestMessages
并在我的演示者中以某种方式合并它们吗?如果是,我该如何管理它,因为我的 Presenter 中没有 Observables(我只传递一个 Observer 作为我的 UseCases 参数)?
用户实体
public abstract class User {
public abstract long id();
public abstract String name();
...
}
消息实体
public abstract class Message {
public abstract long id();
public abstract long senderId();
public abstract String text();
public abstract long timstamp();
...
}
获取用户用例
public class GetUsersUseCase extends UseCaseObservableWithParameter<Boolean, List<User>, UsersRepository> {
@Inject
public GetUsersUseCase(UsersRepository UsersRepository,
@Named("Thread") Scheduler threadScheduler,
@Named("PostExecution") Scheduler postExecutionScheduler) {
super(usersRepository, threadScheduler, postExecutionScheduler);
}
@Override
protected Observable<List<User>> buildObservable(Boolean forceRefresh) {
if(forceRefresh)
repository.invalidateCache();
return repository.getUsers();
}
}
用户演示者
public class UsersPresenter extends BasePresenter<UsersContract.View> implements UsersContract.Presenter {
@Inject
GetUsersUseCase mGetUsersUseCase;
@Inject
UserViewModelMapper mUserMapper;
@Inject
public UsersPresenter() {
}
@Override
public void attachView(UsersContract.View mvpView) {
super.attachView(mvpView);
}
@Override
public void detachView() {
super.detachView();
mGetUsersUseCase.unsubscribe();
}
@Override
public void fetchUsers(boolean forceRefresh) {
getMvpView().showProgress();
mGetUsersUseCase.execute(forceRefresh, new DisposableObserver<List<User>>() {
@Override
public void onNext(List<User> users) {
getMvpView().hideProgress();
getMvpView().showUsers(mUsersMapper.mapUsersToViewModels(users));
}
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
getMvpView().hideProgress();
getMvpView().showErrorMessage(e.getMessage());
}
});
}
}
UseCaseObservableWithParameter
public abstract class UseCaseObservableWithParameter<REQUEST_DATA, RESPONSE_DATA, REPOSITORY> extends UseCase<Observable, REQUEST_DATA, RESPONSE_DATA, REPOSITORY> {
public UseCaseObservableWithParameter(REPOSITORY repository, Scheduler threadScheduler, Scheduler postExecutionScheduler) {
super(repository, threadScheduler, postExecutionScheduler);
}
protected abstract Observable<RESPONSE_DATA> buildObservable(REQUEST_DATA requestData);
public void execute(REQUEST_DATA requestData, DisposableObserver<RESPONSE_DATA> useCaseSubscriber) {
this.disposable.add(
this.buildObservable(requestData)
.subscribeOn(threadScheduler)
.observeOn(postExecutionScheduler)
.subscribeWith(useCaseSubscriber)
);
}
}
用例
public abstract class UseCase<OBSERVABLE, REQUEST_DATA, RESPONSE_DATA, REPOSITORY> {
protected final REPOSITORY repository;
protected final Scheduler threadScheduler;
protected final Scheduler postExecutionScheduler;
protected CompositeDisposable disposable = new CompositeDisposable();
public UseCase(REPOSITORY repository,
@Named("Thread") Scheduler threadScheduler,
@Named("PostExecution") Scheduler postExecutionScheduler) {
Timber.d("UseCase CTOR");
this.repository = repository;
this.threadScheduler = threadScheduler;
this.postExecutionScheduler = postExecutionScheduler;
}
protected abstract OBSERVABLE buildObservable(REQUEST_DATA requestData);
public boolean isUnsubscribed() {
return disposable.size() == 0;
}
public void unsubscribe() {
if (!isUnsubscribed()) {
disposable.clear();
}
}
}
一个问题中有很多问题。让我试着巩固一下我认为我理解的是你的关键问题
实体可以相互引用吗?答案是:是的。也在 干净的架构你可以创建一个实体相互连接的领域模型
UseCase 应该返回什么? 回答:用例定义输入 DTO(数据传输对象)和最适合用例的输出 DTO。鲍勃叔叔在他的书中写道,实体不应传递给用例或从用例中返回
那么主持人的作用是什么?回答:理想情况下,演示者只转换数据。它将对一层最方便的数据转换为对另一层最方便的数据。
希望本指南能帮助您回答您的详细问题
您可以在我最近的帖子中找到更多详细信息和示例: https://plainionist.github.io/Implementing-Clean-Architecture-UseCases/ 和 https://plainionist.github.io/Implementing-Clean-Architecture-Controller-Presenter/
基本上,您想尽可能地推动您的“工具性”感知代码(在圆圈上)。
用例非常接近模型并包含大量业务逻辑 - 您希望这一层非常干净,以便能够快速轻松地进行单元测试。所以,这一层应该对存储一无所知。
但有趣的部分是当 Room 进入房间时 :) Room 使得拥有可以在周围使用的类似模型的对象变得如此容易,如果你使用带注释的 Room,我认为这是一个灰色区域 classes是否适合您的型号。
如果您将 Room 对象视为数据层对象,那么您应该在到达用例之前将它们映射到您的业务对象。 如果您使用 Room 作为 DAO 的内置映射器来建模对象,那么在我看来,您可以在您的用例中使用它们,尽管干净的纯粹主义者可能不会同意这一点。
我的务实建议是 - 如果您的模型具有由多个实体构建的复杂结构,则为其创建一个专用模型 class 并将实体映射到它。 如果您有地址之类的东西,IMO 只需使用房间实体即可。