清洁架构、用例和实体

Posted

技术标签:

【中文标题】清洁架构、用例和实体【英文标题】:Clean Architecture, UseCases and Entities 【发布时间】:2018-06-15 14:04:51 【问题描述】:

好的,所以我刚刚开始了一个新的 android 项目,并想尝试实现 Bob 叔叔的 Clean Architecture。我在使用 RxJava 以及来自 GitHub 示例和样板以及 Fernando Cerjas 的博客(如 this article)的内容方面有了一个不错的开始,但对于如何实现一些用例仍有一些疑问。


TL;DR

实体是否应该具有另一个实体的字段(在我的示例中,User 具有 List<Messages> 字段)?

或者 Presenter 应该结合 UseCases 来构建一个映射到多个实体上的 ViewModel(那么你如何编写映射器代码?)?

或者,Presenter 是否应该有一个与每个 UseCase/Entity 关联的 ViewModel,并创建某种“等待所有数据到 onNext”来为每个 ViewModel 调用 view.show()?


基本上,UseCases 应该只返回实体吗?实体可以由其他实体组成(如在类的字段中)吗?实体只是愚蠢的数据模型 POJO 吗?如何表示“加入 SQL”查询?

举个例子,让我们以一个简单的用户/消息应用为例。 我要实现两个视图:UserListUserDetails

UserList 显示Users 的列表 UserDetails 显示用户的信息及其最新消息。

UserList 非常简单,我可以看到如何编写相关的 UseCase 和层(代码如下)。

我的问题在于UserDetails 屏幕。

如果我希望同时在视图中传递所有数据(例如构建由 User 类和字段 List 组成的 ViewModel),我应该如何编码我的 GetUserInfoUseCaseGetUserInfoUseCase 的返回值应该是多少? 我应该编写Observable<User> GetUserInfoUseCaseObservable<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();
 ...

GetUsersUseCase

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();


UsersPresenter

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();
        
    

【问题讨论】:

【参考方案1】:

一个问题包含很多问题。让我试着巩固一下我认为我理解的是你的关键问题

实体可以相互引用吗?答案是:是的。也在 Clean Architecture 你可以创建一个实体互连的域模型

应该从 UseCase 返回什么? 答:UseCases 定义了对用例最方便的输入 DTO(数据传输对象)和输出 DTO。 bob 叔叔在他的书中写道,实体不应传递给用例或从用例返回

那么演示者的角色是什么?答:理想情况下,演示者只转换数据。它将对一层最方便的数据转换为对另一层最方便的数据。

希望本指南能帮助您回答您的详细问题

您可以在我最近的帖子中找到更多详细信息和示例: https://plainionist.github.io/Implementing-Clean-Architecture-UseCases/ 和 https://plainionist.github.io/Implementing-Clean-Architecture-Controller-Presenter/

【讨论】:

感谢您的回答。关于 UseCases,我发现的几乎每个 Clean Architecture 实现的示例都返回实体,然后演示者将实体映射到视图模型。这只是为了避免实体和世界其他地方之间的另一个 dto 对象吗? 鲍勃叔叔在他的书中“哪些数据跨越边界”一章中写道:“我们不想欺骗和传递实体对象或数据库行。”如果用例交互器返回的 DTO 与实体非常相似,则用例可能需要更少的逻辑【参考方案2】:

基本上,您希望尽可能(在圆圈上)推动您的“工具”感知代码。

用例非常接近模型并包含大量业务逻辑——您希望这一层非常干净,以便能够快速轻松地进行单元测试。所以,这一层应该对存储一无所知。

但有趣的部分是当 Room 进入房间时 :) Room 让你可以很容易地拥有可以在周围使用的类似模型的对象,而且 IMO 如果你是否为你的模型使用 Room 注释类,它是一个灰色区域。

如果您将 Room 对象视为数据层对象,那么您应该在到达用例之前将它们映射到您的业务对象。 如果您使用 Room 作为 DAO 的内置映射器来建模对象,那么 IMO 您可以在您的用例中使用它们,尽管干净的纯粹主义者可能不会同意这一点。

我的实用建议是 - 如果您的模型具有由多个实体构建的复杂结构,则为它有一个专用的模型类并将实体映射到它。 如果您有类似地址的东西,IMO 只需使用 Room 实体即可。

【讨论】:

以上是关于清洁架构、用例和实体的主要内容,如果未能解决你的问题,请参考以下文章

清洁架构:用例输出端口

清洁架构中的“用例交互器”和“服务”有啥区别?

清洁架构用例/领域层的相关性

清洁架构 - Robert Martin - 如何连接用例

Java,设计模式:用例和参与者管理器

清洁架构 - 在使用存储库模式和用例刷新的同时获取缓存数据