Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clean Architecture, UseCases and Entities

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.

How should I code my GetUserInfoUseCase if I want all the data to be passed at the view at the same time (like building a ViewModel composed of a User class, with a field List)? What should be the return value of the GetUserInfoUseCase? Should I code a Observable<User> GetUserInfoUseCase and a Observable<List<Message>> GetUserLatestMessages and merge them somehow in my presenter? If yes, how can I manage this, as I don't have the Observables in my Presenter (I'm passing only an Observer as my UseCases parameters)?

User Entity

public abstract class User {
    public abstract long id();
    public abstract String name();
 ...
}

Message Entity

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();
        }
    }
}
like image 936
w00ly Avatar asked Jan 05 '18 18:01

w00ly


People also ask

What are entities in clean architecture?

The Clean Architecture Entities — Describe the enterprise business rules or the business objects of the app, encapsulating the most general and high-level rules. They are the least likely to change when there are external changes. Use Cases — The app-specific business rules.

What are the four layers of clean architecture?

Clean architecture vs. The logical layers of this style are as follows: Presentation layer ( accounts for the presentation to the user) Business logic layer (contains the business rules) Data access layer (processes the communication with the database)

What are the clean architecture essentials?

The main rule of clean architecture is that code dependencies can only move from the outer levels inward. Code on the inner layers can have no knowledge of functions on the outer layers. The variables, functions and classes (any entities) that exist in the outer layers can not be mentioned in the more inward levels.

What are entities use cases?

A use case diagram shows the interaction between the system and entities external to the system. These external entities are referred to as actors. Actors represent roles which may include human users, external hardware or other systems.


2 Answers

Quite a lot questions within a single question. let me try to consolidate what I think I understood are ur key questions

  • Can Entities reference each other? the answer would be: YES. Also in Clean Architecture u can create a domain model where entities are interconnected

  • What should be returned from a UseCase? Answer: UseCases define input DTOs (Data transfer objects) and output DTOs which are most convenient for the use case. in his book uncle bob writes that entities should not be passed to use cases or returned from use cases

  • What is the role of the presenter then? Answer: ideally a presenter is converting data only. It converts data which is most convenient for one layer into data which is most convenient for the other layer.

hope this guidance helps u to answer ur detailed questions

More details and examples you can find in my recent posts: https://plainionist.github.io/Implementing-Clean-Architecture-UseCases/ and https://plainionist.github.io/Implementing-Clean-Architecture-Controller-Presenter/

like image 170
plainionist Avatar answered Sep 23 '22 04:09

plainionist


Basically, you want to push your "instrumental" aware code as far as possible (on the circle).

Use cases are very close to the model and contain a lot of business logic - you want this layer very clean to be able to do quick and easy unit tests. So, this layer shouldn't know anything about storage.

But the fun part is when Room enters the room :) Room makes it so easy to have model-like objects that you can use around and IMO it's a grey area should you use Room annotated classes for your model or not.

If you think about Room objects as Data Layer objects, then you should map them to your business objects before reaching use cases. If you use Room as a built-in mapper of DAO to model objects, then IMO you can use them in your use cases, although clean purists probably would not agree on this.

My pragmatic advice would be - if your model has a complex structure built in from multiple entities then have a dedicated model class for it and map entities to it. If you have something like an Address, IMO just go with the Room entity.

like image 23
daneejela Avatar answered Sep 23 '22 04:09

daneejela