Вопрос:

Clean Architecture, UseCases and Entities

android architecture clean-architecture

2877 просмотра

1 ответ

140 Репутация автора

Okay, so I just started a new Android project and wanted to try implementing the Clean Architecture by Uncle Bob. I have a nice beginning using RxJava and stuff from GitHub samples & boilerplates and Fernando Cerjas' blog (like this article), but still have some questions on how to implement some UseCases.


TL;DR

Should an Entity have fields that are another Entity (in my example, User having a List<Messages> field)?

Or should the Presenter combine UseCases to build a ViewModel mapped on multiple Entities (then how to you code the mapper?)?

Or should the Presenter have a ViewModel associated to each UseCase/Entity, and create some kind of "wait for all data to onNext" to call the view.show() for each ViewModel?


Basically, should UseCases only return Entities? Can an Entity be composed of other entities (as in a field of the class)? Are Entities only dumb datamodels POJOs? How to you represent 'join SQL' queries?

As an example, let's take a simple users/messages app. I want to implement two views: UserList and UserDetails:

  • UserList displays a list of Users
  • UserDetails displays a user's information and its latest messages.

UserList is pretty straightforward, and I can see how to code the associated UseCase and layers (code below).

My problem is with the UserDetails screen.

Как мне кодировать мой код, GetUserInfoUseCaseесли я хочу, чтобы все данные передавались в представлении одновременно (например, создание ViewModel, состоящей из класса User с полем List)? Каким должно быть возвращаемое значение GetUserInfoUseCase? Должен ли я кодировать a Observable<User> GetUserInfoUseCaseи a 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();
 ...
}

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

UseCase

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();
        }
    }
}
Автор: w00ly Источник Размещён: 05.01.2018 06:07

Ответы (1)


5 плюса

895 Репутация автора

Решение

Довольно много вопросов в одном вопросе. позвольте мне попытаться закрепить то, что, я думаю, я понял, это ваши ключевые вопросы

  • Могут ли сущности ссылаться друг на друга? ответ будет: ДА. Также в чистой архитектуре вы можете создать модель предметной области, в которой сущности взаимосвязаны

  • Что должно быть возвращено из UseCase? Ответ: UseCases определяют входные DTO (объекты передачи данных) и выходные DTO, которые наиболее удобны для варианта использования. в своей книге дядя Боб пишет, что сущности не должны передаваться в сценарии использования или возвращаться из сценариев использования

  • Какова роль ведущего тогда? Ответ: в идеале докладчик конвертирует только данные. Он преобразует данные, наиболее удобные для одного уровня, в данные, наиболее удобные для другого уровня.

надеюсь, что это руководство поможет вам ответить на ваши подробные вопросы

Более подробную информацию и примеры вы можете найти в моих последних сообщениях: https://plainionist.github.io/Implementing-Clean-Architecture-UseCases/ и https://plainionist.github.io/Implementing-Clean-Architecture-Controller-Presenter /

Автор: plainionist Размещён: 02.02.2018 07:05
Вопросы из категории :
32x32