Android-CleanArchitecture icon indicating copy to clipboard operation
Android-CleanArchitecture copied to clipboard

Pass an object from the Presentation Layer -> Domain Layer -> Data Layer

Open lalongooo opened this issue 9 years ago • 33 comments

Hi @android10,

I have a couple of questions regarding how to pass an object from the presentation layer to the domain layer and then to the data layer. I have a use case when a user signs up within my app.

  1. Pass and object from the domain layer to the data layer:

    This is the interface (in the domain layer) between the domain and the data layer:

    public interface UserRepository {
        //The User class belongs to the Domain Layer
        Observable<User> signUp(User user);
    }
    

    ...and this is the UseCase subclass:

    public class SignUpUseCase extends UseCase {
        // Members set up in the SignUpUseCase constructor
        private User user;
        private UserRepository userRepository;
    
        @Override
        protected Observable buildUseCaseObservable() {
            return userRepository.signUp(user);
        }
    }
    

    Now, in the data layer, the implementation of the UserRepository interface (of the domain layer) is defined as follows:

    public class UserDataRepository implements UserRepository {
    
        private final UserEntityDataMapper userEntityDataMapper;
    
        public Observable<User> signUp(User user){
    
            final UserDataStore userDataStore = userDataStoreFactory.createCloudDataStore();
    
            // Declared variable to make this code easier to read
            UserEntity transformedUserEntity = userEntityDataMapper.transform(user)
    
            return userDataStore.signUp(transformedUserEntity)
                .map(userEntity -> this.userEntityDataMapper.transform(userEntity));
        }
    }
    

    This takes me to define 2 methods in the UserEntityDataMapper class as follows:

    public User transform(UserEntity userEntity) {
      ...
    }
    
    public UserEntity transform(User user) {
      ...
    }
    

    Is this a correct implementation on how to pass objects from the domain to the data layer?

  2. Pass and object from the presentation layer to the domain layer:

    For the SignUpUseCase use case, I need a User (defined in the domain layer) to be supplied via it's constructor in the UserModule.

    @Module
    public class UserModule {
    
        //The UserModel class belongs to the Presentation Layer
        private UserModel userModel;
    
        @Provides
        @PerActivity
        @Named("signUp")
        UseCase provideSignUpUseCase(UserRepository userRepository, ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread){
            return new SignUpUseCase(userModel, userRepository, threadExecutor, postExecutionThread);
        }
    }
    

    But then, I'd need a User mapper to transform a UserModel (defined in the presentation layer) to a User (defined in the domain layer)

    Where should I place this User mapper, in the UserModule or in the SignUpUseCase class?

lalongooo avatar Nov 11 '15 03:11 lalongooo

  1. Passing from Domain to Data

This is correct, Data receives an object from Domain and maps it to whatever Entity the data implementation expects.

  1. Passing from Presentation to Domain

If I am reading it correctly, UserModule is the Dagger Injection Module for the User scope, so you should not hold instances of other classes in that UserModule. It exists purely to provide instances to inject.

You are not obliged to keep to the protected Observable buildUseCaseObservable() format, since this makes it hard to use when you have dynamic objects that need to be passed to a UseCase. What i would do is have a method which takes a User from the Presentation Layer and have a UserMapper declared in the Presentation Layer which would transform the UserModel to a User and pass that on to the Domain Layer.

So the flow would be

Presentation layer

  • user fills in some fields and clicks OK
  • the presenter gathers the data and constructs a UserModel
  • the UserModel is passed to a mapper to convert it to a User
  • SignUpUseCase.signUp(user) is called

Domain layer

  • SignUpUseCase receives the User
  • ( here you could apply more business rules first)
  • the UseCase transforms UserModel to User and passes it to your Service

Data layer

  • UserService receives the User
  • User is used to sign up in the cloud via the Service
  • On response, the SignUpUseCase is notified

Domain layer

  • SignUpUseCase processes the result and applies any business logic (let's assume the signup was a success)
  • our implementation requires us to save the User locally, so we pass it to the UserRepository
  • probably after this, we notify the presenter of the result

Data layer

  • UserRepository receives the processed User
  • User is transformed to UserEntity ( this could be implementation specific )
  • UserEntity is saved locally in cache to be retrieved later. I hope this helps a bit.

edit was to reflect remark from @lalongooo made below.

Trikke avatar Nov 18 '15 15:11 Trikke

Thanks @Trikke , I'll give it a try later today and let you know the results.

lalongooo avatar Nov 18 '15 16:11 lalongooo

@lalongooo I have made an example for your need. Hope giving you some help. #55

johnwatsondev avatar Nov 19 '15 01:11 johnwatsondev

@johnwatsondev It's a nice approach when passing a couple of params, but...what if you need to pass an object with a lot of properties/members..?

lalongooo avatar Nov 19 '15 22:11 lalongooo

@Trikke If I let the SignUpUseCase to receive the Usermodel...wouldn't this be breaking the dependency rule?

I mean, the domain layer would be depending on a class of the presentation layer.

untitled

lalongooo avatar Nov 19 '15 22:11 lalongooo

@lalongooo yes, i have made a mistake as was a bit too quick and forgot about the boundaries. In the Presentation Layer, you could have a mapper (something like UserModelMapper), which takes a UserModel from the Presenter and maps it to a User which the Domain will understand. This User is then passed as an argument to the UseCase. This way the Domain doesn't know about the data from Presentation, which is correctly converted at the boundary.

I'll adjust my comment above to reflect your correct remark.

Trikke avatar Nov 20 '15 09:11 Trikke

@Trikke @lalongooo @android10 Hi everyone, IMO the implementation doesn't need the UserModel (Presentation layer) and it's mapper. The dependency rule states that nothing in an inner circle can know anything at all about something in an outer circle, but, the opposite is possible. I think it's possible to call the User (Domain layer) inside the presentation layer. Plus, the UserModel and User have the same attributes. Technically, it's a dupicate and this is a violation of DRY. please correct me if i'm wrong.

RamiJemli avatar Nov 24 '15 18:11 RamiJemli

The only thing I would add here is that a UserRepository should not know anything about signing up users, it is a repository and its responsibility is to work with data sources. The login process is part of your domain and the repository will "save" user session.

android10 avatar Nov 24 '15 22:11 android10

I think the UserModel and mapper is mechanical and dogmatic. If the user's data has changed(eg: add age attribute), you must change your user(UserMode-Presentation、User-Domain、UserEntity-Data) and its mapper in three layers,its unacceptable! In MVP pattren, the model is an interface defining the data to be displayed or otherwise acted upon in the user interface. (Considering the layered architecture) Model is the embodiment of the data and business, and from the next layer of dependence. We can make it simple and dependency rules are correct, not so many template code(“user” and its "mapper").

zhengxiaopeng avatar Nov 25 '15 04:11 zhengxiaopeng

@android10 You mean with "save" user session the request to the cloud but the domain model would create for example the preferences session for the user (login process)

spirosoik avatar Nov 25 '15 07:11 spirosoik

@spirosoik yes. My comment was mostly a naming convention. A repository abstracts the origin of your data and should not know anything about login any user :)

android10 avatar Nov 25 '15 10:11 android10

@android10 exactly. Thanks great :+1: . By the way frodo rocks

spirosoik avatar Nov 25 '15 10:11 spirosoik

@spirosoik :smile: Thank you!

android10 avatar Nov 25 '15 10:11 android10

@spirosoik I would like to add that the repository pattern uses a metaphor of a Collection. When it comes to naming conventions, use add/remove/etc. methods. It's like you're dealing with a Collection. Never pass id as int because this is not DAO, pass an object instead.

RamiJemli avatar Nov 25 '15 10:11 RamiJemli

@RamiJemli Yes I am following the same.

spirosoik avatar Nov 25 '15 11:11 spirosoik

@RamiJemli @spirosoik I probably understand this abstract strategy. So the data layer method will not same as domain layer method naming.

Never pass id as int because this is not DAO, pass an object instead.

Your meaning is data layer never know the param's logic meaning. So id is not always int. It will clarify if there is a sample of this topic about User Login Process. Thanks for giving advice.

johnwatsondev avatar Nov 25 '15 14:11 johnwatsondev

@johnwatsondev

The flow on the topic about "User Login Process" ( or any other action on an external service) is very broad depending on implementation requirements, but usually boils down to something like this :

As @android10 remarked, the Repository pattern is just a way to abstract data/loading saving. So we need a separate Service ( or Service Agent ) to implement the logic for "User Login" (usually, talk to an api and get a response back). Where this Service should exists largely depends on your architecture, but in this github repository, i'd wager to just put it in the Data Layer. And then the following generalisation happens :

Domain layer

  • SignUpUseCase receives the User
  • ( here you could apply more business rules first)
  • the UseCase transforms UserModel to User and passes it to your Service

Data layer

  • UserService receives the User
  • User is used to sign up in the cloud via the Service
  • On response, the SignUpUseCase is notified

Domain layer

  • SignUpUseCase processes the result and applies any business logic (let's assume the signup was a success)
  • our implementation requires us to save the User locally, so we pass it to the UserRepository
  • probably after this, we notify the presenter of the result

Data layer

  • UserRepository receives the processed User
  • User is transformed to UserEntity ( this could be implementation specific )
  • UserEntity is saved locally in cache to be retrieved later.

Trikke avatar Nov 25 '15 15:11 Trikke

@johnwatsondev I absolutely agree. Basically in this resository Service (API) is coupled with data layer as a CloudDatastore.

spirosoik avatar Nov 25 '15 16:11 spirosoik

@Trikke Thanks for your comment! Is is really helpful and clear.

@android10 what do you think about @zhengxiaopeng 's comment? Even when you have said...

It is worth mentioning that each layer uses its own data model so this independence can be reached (you will see in code that a data mapper is needed in order to accomplish data transformation, a price to be paid if you do not want to cross the use of your models over the entire application). Here is an schema so you can see how it looks like:

lalongooo avatar Nov 25 '15 17:11 lalongooo

@zhengxiaopeng what is your solution then?

android10 avatar Nov 25 '15 21:11 android10

@android10 :-) I am cognizing this question. The following is my view now:

  • In M-V-X pattern and 3/N Tiers Architecture, the model classes(just model classes) are base on business rule and data access. Model classes may not be defined in presentation layer.
  • "The models are likely just data structures that are passed from the controllers to the use cases, and then back from the use cases to the presenters and views." - The Clean Architecture, and Request modelResponse model - Robert C Martin - Clean Architecture(42:46). So, remove the user's classes in presentation layer and then put them into domain layer. Mostly, one user model is enough.
  • In data layer, conservatively, retain UserEntity. The data object class(UserEntity) is the starting point to solve the problem which user class violate DRY principle. but I think this should be specifically contemplated in the different application. @RamiJemli
  • Otherwise, DTO pattern is a way for entity translator(some case).

Other reference: Entity Translator Using the Entity Framework in n-Tier Client-Side Applications Using the Entity Framework in n-Tier ASP.NET Applications mobile application development architecture

zhengxiaopeng avatar Nov 26 '15 15:11 zhengxiaopeng

I'm a strong believer of having View Models and each layer should have its model to deal with. So I avoid coupling between layers. If you share Domain models with UI models, you are breaking the dependency rule because Domain knowing something about outer layers in the circle:

untitled

You will have to make changes anyway when adding new information to your UI.

android10 avatar Nov 26 '15 17:11 android10

Alright, I agree the dependency rule. I want to add that we(Android Dev) are just writing a single application, we don't have Front-end developer and Backend developer and so on. All codes(layers) are transparent to us。

zhengxiaopeng avatar Nov 27 '15 03:11 zhengxiaopeng

@zhengxiaopeng I agree with you. @android10 Your great work got me interested in Clean architechture, so i looked deeper into this. When you call the domain model inside the presentation layer, it's the outer circle knowing something about the inner circle. This doesn't violate the dependency rule, but the opposite does.

RamiJemli avatar Nov 27 '15 06:11 RamiJemli

@RamiJemli yup

android10 avatar Nov 27 '15 11:11 android10

Hello,

I don't if my question should be asked here (tell me if I'm wrong). My question is about using dynamic parameters in a use case, like @lalongooo I have a SignUpUseCase, and this use case need user's email and password.

If I take UserModule that have the responsibility to create GetUserDetails an Id is needed to get a specific user, And this Id is specified when UserModule is created.

private void initializeInjector() {
    this.userComponent = DaggerUserComponent.builder()
        .applicationComponent(getApplicationComponent())
        .activityModule(getActivityModule())
        .userModule(new UserModule(this.userId))
        .build();
  }

In my case when SignModule is created I don't already know the user's password and email, so I can't pass it through the constructor.

So how can I proceed to pass, user's email and password to my use case when the user hit the button create an account ?

MehdiChouag avatar Nov 27 '15 12:11 MehdiChouag

This may help you @MehdiChouag https://github.com/android10/Android-CleanArchitecture/issues/32

lalongooo avatar Nov 27 '15 16:11 lalongooo

I'll take a look at this thank you :smile:

MehdiChouag avatar Nov 27 '15 19:11 MehdiChouag

I had a really interesting conversation around this no long ago. I can give you an example where having mappers everywhere might not be totally correct approach.

The case is that we were working with a HATEOS type of backend on which the urls are not constructed by you, but the data flows and hrefs from where to retrieve data are given to you by the backend instead. Now the question boiled down to the following:

Imagine you only have knowledge of a single entry point to retrieve a master JSON file which contains links to perform different actions like login, register, fetch home or what ever. Imagine a case where due to a certain action of the user on an item (which remember, the collection URL was given to you by the backend previously), you want to display a full blown details page for the item selected. You have no idea how to construct the url but the URL has been given to you in the item response as a "self" attribute. You have 3 options as I see it:

  1. Create mappers between all layers as suggested and include the self link into the presentation and domain layer object to later on let the domain pass that link to the data layer, so finally the data layer can make use of that link to actually fetch the content. The funny thing, is that you are including a href in the presentation and domain layers objects which has no meaning at all for those tiers.
  2. Include an instance of the entity object inside the domain object and an instance of the domain object inside the presentation object. This way you keep a reference to the object from the tier below (we break the layer isolation but the dependencies rules are still maintain). Then when calling a method in the layer below, you could just simply pass the object which corresponds to that layer.
  3. (Not recommended) Store every single object/collection you retrieve in memory/disk. Then through the id of the item, the data layer could traverse the content stored and get the self link directly for that content from there. This way neither the domain or presentation layer have knowledge of this href value which has no meaning for them.

I don't have a strong opinion between 1 and 2 but definitely is a problem we had to approach.

Comments/suggestions?

ghost avatar Feb 05 '16 15:02 ghost

What do you do when you have domain specific data? For example domain level entities have some metadata, while UI level does not need it. How do you save that metadata to be able recreate entity from UI level model?

guliash avatar Dec 19 '16 15:12 guliash