It is architecture based on the book and blog by Uncle Bob. It is a combination of concepts taken from the Onion Architecture and other architectures. The main focus of the architecture is separation of concerns and scalability. It consists of four main modules: App
, Domain
, Data
, and Device
.
Source code dependencies only point inwards. This means inward modules are neither aware nor dependent on outer modules. However, outer modules are both aware and dependednt on inner modules. Outer modules represent the mechanisms by which the business rules and policies (inner modules) operate. The more you move inward, the more abstraction is present. The outer you move the more concrete implementations are present. Inner modules are not aware of any classes, functions, names, libraries, etc.. present in the outer modules. They simply represent rules and are completely independent from the implementations.
The Domain
module defines the business logic of the application. It is a module that is independent from the development platform i.e. it is written purely in the programming language and does not contain any elements from the platform. In the case of Flutter
, Domain
would be written purely in Dart
without any Flutter
elements. The reason for that is that Domain
should only be concerned with the business logic of the application, not with the implementation details. This also allows for easy migration between platforms, should any issues arise.
Domain
is made up of several things.
- Entities
- Enterprise-wide business rules
- Made up of classes that can contain methods
- Business objects of the application
- Used application-wide
- Least likely to change when something in the application changes
- Usecases
- Application-specific business rules
- Encapsulate all the usecases of the application
- Orchestrate the flow of data throughout the app
- Should not be affected by any UI changes whatsoever
- Might change if the functionality and flow of application change
- Repositories
- Abstract classes that define the expected functionality of outer layers
- Are not aware of outer layers, simply define expected functionality
- E.g. The
Login
usecase expects aRepository
that haslogin
functionality
- E.g. The
- Passed to
Usecases
from outer layers
Domain
represents the inner-most layer. Therefore, it the most abstract layer in the architecture.
App
is the layer outside Domain
. App
crosses the bounderies of the layers to communicate with Domain
. However, the Dependency Rule is never violated. Using polymorphism
, App
communicates with Domain
using inherited class: classes that implement or extend the Repositories
present in the Domain
layer. Since polymorphism
is used, the Repositories
passed to Domain
still adhere to the Dependency Rule since as far as Domain
is concerned, they are abstract. The implementation is hidden behind the polymorphism
.
Since App
is the presentation layer of the application, it is the most framework-dependent layer, as it contains the UI and the event handlers of the UI. For every page in the application, App
defines at least 3 classes: a Controller
, a Presenter
, and a View
.
- View
- Represents only the UI of the page. The
View
builds the page's UI, styles it, and depends on theController
to handle its events. TheView
has-aController
. - In the case of Flutter
- The
View
is comprised of 2 classes- One that extends
StatefulWidget
, which would be the rootWidget
representing theView
- One that extends
State
with the template specialization of the other class.
- One that extends
- The
State
contains thebuild
method, which is technically the UI StatefulWidget
contains theState
as perFlutter
- The
StatefulWidget
only serves to pass arguments to theState
from other pages such as a title etc.. It only instantiates theState
object (theView
) and provides it with theController
it needs. - The
StatefulWidget
has-aState
object (theView
) which has-aController
- In summary, both the
StatefulWidget
and theState
together represent theView
of the page.
- The
- Represents only the UI of the page. The
- Controller
- Every
View
has-aController
. TheController
provides the needed member data of theView
i.e. dynamic data. TheController
also implements the event-handlers of theView
widgets, but has no access to theWidgets
themselves. TheView
uses theController
, not the other way around. When theView
calls a handler from theController
, it wraps it with acallHandler(fn)
function if theView
(the scaffhold) needs to be rebuilt by callingsetState()
before calling the event-handler. - Every
Controller
extends theController
abstract class, which implementsWidgetsBindingObserver
. EveryController
class is responsible for handling lifecycle events for theView
and can override:- void onInActive()
- void onPaused()
- void onResumed()
- void onSuspending()
- Also, every
Controller
has to implement initListeners() that initializes the listeners for thePresenter
for consistency. - The
Controller
has-aPresenter
. TheController
will pass theRepository
to thePresenter
, which it communicate later with theUsecase
. TheController
will specify what listeners thePresenter
should call for all success and error events as mentioned previously. Only theController
is allowed to obtain instances of aRepository
from theData
orDevice
module in the outermost layer.
- Every
- Presenter
- Every
Controller
has-aPresenter
. ThePresenter
communicates with theUsecase
as mentioned at the beginning of theApp
layer. ThePresenter
will have members that are functions, whicha optionally set by theController
and will be called if set upon theUsecase
sending back data, completing, or erroring. - The
Presenter
is comprised of two classesPresenter
e.g.LoginPresenter
- Contains the event-handlers set by the
Controller
- Contains the
Usecase
to be used - Intitializes and executes the usecase with the
Observer
class and the appropriate arguments. E.g. withusername
andpassword
in the case of aLoginPresenter
- Contains the event-handlers set by the
- A class that implements
Observer<T>
- Has reference to the
Presenter
class. - Implements 3 functions
- onNext(dynamic)
- onComplete()
- onError()
- These 3 methods represent all possible outputs of the
Usecase
- If the
Usecase
returns an object, it will be passed toonNext(dynamic)
. - If it errors, it will call
onError()
. - Once it completes, it will call
onComplete()
.
- If the
- These methods will then call the corresponding methods of the
Presenter
that are set by theController
. This way, the event is passed to theController
, which can then manipulate data and update theView
- Has reference to the
- Every
- Extra
Utility
classesConstants
classesNavigator
(if needed)
Represents the data-layer of the application. The Data
module, which is a part of the outermost layer, is responsible for data retrieval. This can be in the form of API calls to a server, a local database, or even both.
- Repositories
- Every
Repository
should implementRepository
from the Domain layer. - Using
polymorphism
, these repositories from the data layer can be passed accross the bounderies of layers, starting from theController
down to theUsecases
through thePresenter
. - Retrieve data from databases or other methods.
- Responsible for any API calls and high-level data manipulation such as
- Registering a user with a database
- Uploading data
- Downloading data
- Handling local storage
- Calling an API
- Every
- Models (not a must depending on the applicaiton)
- Duplicates of
Entities
with the addition of extra members that might be platform-dependent. For example, in the case of local databases, this can be manifested as anisDeleted
or anisDirty
entry in the local database. Such entries cannot be present in theEntities
as that would violate the Dependency Rule since Domain should not be aware of the implementation. - In the case of our application, models in the
Data
layer will not be necessary as we do not have a local database. Therefore, it is unlikely that we will need extra entries in theEntities
that are platform-dependent.
- Duplicates of
- Mappers
- Map
Entity
objects toModels
and vice-versa. - Static classes with static methods that receive either an
Entity
or aModel
and return the other. - Only necessary in the presence of
Models
, which are not present in our case.
- Map
- Extra
Utility
classes if neededConstants
classes if needed
Part of the outermost layer, Device
communicates directly with the platform i.e. Android and iOS. Device
is responsible for Native functionality such as GPS
and other functionality present within the platform itself like the filesystem. Device
calls all Native APIs.
- Devices
- Similar to
Repositories
inData
,Devices
are classes that communicate with a specific functionality in the platform. - Passed through the layers the same way
Repositories
are pass across the bounderies of the layer: using polymorphism between theApp
andDomain
layer. That means theController
passes it to thePresenter
then thePresenter
passes it polymorphically to theUsecase
, which recieves it as an abstract class.
- Similar to
- Extra
Utility
classes if neededConstants
classes if needed
- Login
- Login Button in
View
- Button handler in
Controller
Controller
callslogin
method in thePresenter
Presenter
executes theLoginUsecase
, passing it any parametes, theRepository
and using itsObserver<T>
class.- The
Usecase
uses theRepository
in its logic to login. - The
Usecase
fires the appropriate events that are handled in thePresenter
, which fires corresponding events handled in theController
- The
View
updates itself automatically when the cycle is done using itssetState()
method.
- Login Button in
Use this as a reference to the Dependency Rule and NOT as a mechanism to write the classes. Use the above docs to write the classes.