Skip to content

Mobile Architecture

Mathieu BΓ©langer edited this page Nov 23, 2020 · 3 revisions

The following proposed architecture is intended to maximize code re-usability between different Dart client applications. Since, we are using a no back-end architecture for our current app, it is specially important to increase code re-usability between all of the client applications.

🧊 Bloclibary suggested architecture

A good place to start when talking about app's architecture using the BLoCs library is the bloclibrary advices on architecture:

bloclibrary_architecture

This architecture is great because it decouples the application and the infrastructure layer. However, this architecture has the disadvantage of coupling the business logic with the rest of the application logic by grouping it within the same layer (in the BLoCs layer). Since BLoCs are, in a way, linked to the view, it will sometime be different between clients (some client may decide to implement a form in different ways for example). Wouldn't it be better if we could have all of the business logic in its own layer. It would increase code re-usability since our bloc could change depending on the client but the business logic would be shared between all of them (and thus, change to our code base would only need to be made once). Just think about some cloud functions that would need to use the same business logic as our mobile and web app. We would not want them to use BLoCs... That is why an independent business logic layer is so important in our case. Also, it will enable us to switch from a client only architecture to a Dart back-end more easily, if we need and want to.

πŸ’‘ Our architecture proposal

This architecture is a mix of different things we found and discussed. It is mainly inspired by some of the DDD patterns and the dependency inversion principle.

app_architecture

The thing that is better with this one is that the application layer is completely independent from the infrastructure layer since each of them only depend on the domain layer and thus it is possible to change the "implementation details" of the infrastructure layer without affecting other layers of our app. To do so, we typically create interfaces in the domain layer that are implemented by the infrastructure layer and used in the application layer as it is prescribed by the dependency inversion principle.

Like we said having an independent domain layer also makes the business logic more "shareable" between different application layers.

πŸ‘“ View:

  • Pure dumb Flutter code without logic (we will try to perform data validation (like form validation) in the domain layer if this validation is linked to business logic rules). The view is specific to the framework and the platform (or can be shared between platform in case of Flutter).

🧠 Application:

  • BLoCs can be specific to the app or can be shared and reused between Dart based app. In the case of cloud functions, the function correspond to this layer (since we want our function to only be using the domain layer).

πŸ’Ό Domain: See EvansClassification...

  • Entities:
    • Implements business logic in an object oriented way by creating abstract objects (that belongs to the domain expertise). Every single Entity has its own identity and thus use reference equality.
  • ValueObjects:
    • These are things like a phone number, money and email. They implement value equality since two of these objects can be interpreted as the same.
  • RepositoryInterfaces:
    • These interfaces implements the requirements for the repositories to meet. They make unit testing more easy to implement as it is easy to mock data providers using these interfaces.

πŸ—οΈ Infrastructure:

  • Repositories:
    • Their responsibility is to implement the interface provided by the domain and to create domain entities from the information returned by the data providers using DTOs. These implementations are simply "injected" in the application layer so they can be used there.
  • Data transfer objects:
    • Their role is to serialize/deserialize information (e.g.: serialize a Firebase DocumentSnapshot to DTO with a myDto.fromFirestore(DocumentSnapshot snapshot) and deserialize it to a domain entity object with something like myDto.toDomain()).
  • Data providers:
    • Their role is to perform the CRUD operations and perform the link to the data sources (like firestore, SQL data bases, web API, mobile's native functionality, etc.)
    • Often not even from our code base (like the cloud_firestore library or libraries that offer an interface to a phone's native features for example).

🌐 External services:

  • These are things that do not come from our code base. E.g.: phone native features like camera, file system, geolocation, etc. Web API and databases are also obvious external services.

Such a rigorous layered architecture may seems like a bit exaggerated but remember this enables us to share our code between multiple platforms and framework and that it enables us to not use any kind of back-end for our app.

πŸ“ Example of project structure:

(This is just an example, to get the idea...) Let's say we need a folder structure to support Flutter mobile and web with some specific features for the mobile version and some other for the web one.

my_app/lib/src/
β”œβ”€β”€ mobile/
β”‚   β”œβ”€β”€ presentation/
β”‚   β”‚   β”œβ”€β”€ my_feature_1_form/
β”‚   β”‚   β”‚   └── my_feature_1_form.dart
β”‚   β”‚   └── .../
β”‚   β”œβ”€β”€ application/
β”‚   β”‚   β”œβ”€β”€ my_feature_1/
β”‚   β”‚   β”‚   β”œβ”€β”€ my_feature_1_form_event.dart
β”‚   β”‚   β”‚   β”œβ”€β”€ my_feature_1_form_state.dart
β”‚   β”‚   β”‚   └── my_feature_1_form_bloc.dart
β”‚   β”‚   └── .../
β”‚   └── infrastructure/    <-- For what is really specific to mobile devices (e.g.: geolocation)
β”‚       β”œβ”€β”€ my_ressource/
β”‚       β”‚   β”œβ”€β”€ my_ressource_dto.dart
β”‚       β”‚   └── my_ressource_repository.dart
β”‚       └── .../
β”œβ”€β”€ web/
β”‚   β”œβ”€β”€ views/
β”‚   └── application/
└── common/
    β”œβ”€β”€ presentation/
    β”œβ”€β”€ domain/
    β”‚   └── my_domain_1/
    β”‚       β”œβ”€β”€ i_my_ressource_repostory.dart   <-- It's an interface to my_ressoure_repository.dart
    β”‚       β”œβ”€β”€ some_entity.dart
    β”‚       └── some_value_object.dart
    β”œβ”€β”€ infrastructure/
    └── application/

πŸ“š Further readings

These were the main sources of inspiration for this architecture: