56  comments

Firebase ? allows developers to get their apps working quickly. Flutter ? does the same thing. But, as it goes in life, trade-offs are everywhere. Sure, you can hack something together in a dash of enthusiasm but this initial excitement will fade away as soon as you get totally lost in your own code.

Flutter apps need structure that is easy to orient yourself in, testable and maintainable. It also wouldn't hurt if the way you architect your Flutter apps allows for adding new features without a headache. Especially with a client-centric service such as Firebase Firestore, it's extremely important to keep your code clean. Let's do it by following the principles of Domain-Driven Design.

This post is a part of a series. See all the parts ? here ?

The project we will build

We're going to build a fairly complex note taking application featuring things such as

  • Real-time Firestore data Streams
  • Extensive data validation for a rich user experience
  • Google & email + password authentication
  • Reorderable todo lists and much more

 A video is worth a thousand words, as they say, so before getting into the details of how DDD fits into all of this...

Domain-Driven Design - the big picture

You have surely seen or built Flutter apps using Firebase Auth and Firestore and I'm sure you didn't put the Firebase calls into your widget code. In other words, you used some sort of an architecture.

You may have even watched my Clean Architecture course where we worked with a simple REST API. Over there, the class dependency flow together with the file and folder structure were neatly outlined for you and it's possible to use it with Firebase too. How is Domain-Driven Design (DDD) different then?

Simply said, it's better on every level. We still have a good separation into layers which brings beautiful traits ease of navigation and testability. So no, we still don't put all of our code into the UI or state management.

We will go over everything in more detail later but for now just know that the diagram below outlines the key architectural layers present in a DDD Flutter app. We're going to use BLoC in this series but, as usual, I didn't forget about all of you who have declared BLoC a sworn enemy.  If you don't want to use it in your apps, feel free to use a view model of your choice, whether it be ChangeNotifier, MobX Store, or even the new StateNotifier. More on that later.

There are a few things that I couldn't fit on the diagram. Namely:

  • Arrows represent the flow of data. This can be either uni-directional or bi-directional.
  • The domain layer is completely independent of all the other layers. Just pure business logic & data.

If you're familiar with my Clean Architecture course, the above diagram feels somewhat familiar. If you aren't, no worries. It's not a prerequisite for this tutorial series. Note that classes such as Firestore or FirebaseAuth are ready-made data sources, so we will write code from repositories upwards.

Notice that in addition to holding and carrying around data, Entities and validated ValueObjects also contain logic. This ranges from data validation and helpers to complex computations.
Also take note of how Exceptions are put into the regular flow of data as Failures. The only place for try and catch statements are Repositories. This will make it impossible not to handle exceptions, which is a very good thing.

Before we go ahead and take a closer look at what are the roles of all the layers and their classes, let's first tackle the age old question of folder structure.

Folder structure

I'm the first to admit that the folder structure outlined in the Clean Architecture course is a pain to deal with. With DDD, we're going to take a different approach.

Layers will hold features, not the other way around. This will still keep the code readable but, most importantly, it will ensure that adding more features and sub-features is going to be a pure bliss!

Let's take a look at the notes feature. While the main notes folder is present inside every layer (application, domain, infrastructure, presentation), its subfolders are different!

What does this mean? Well, we can have both a good folder structure and a separation into architectural layers at the same time ??

It's also worth noting that some features don't even have to necessarily be represented in all layers. Notice that splash folder in the presentation layer? There is no inherent "splash screen logic", so it doesn't make sense to put it into other layers.

All in all, we can mix and match the dependencies in between features as long as we keep true to the dependency flow outlined in the diagram above (domain layer has to have ZERO dependencies on other layers).

Architectural Layers

Unlike with the spaghetti architecture ?, you will always know where to put a certain class when you're following the Domain-Driven Design principles. Each one of the layers has a clear-cut responsibility. As you can see on the folder structure picture above, every architectural layer contains features and possibly a core folder which holds classes common to all the features in that layer (helpers, abstract classes, ...).

Presentation

This layer is all Widgets ? and also the state of the Widgets. I already mentioned that we're going to use BLoC in this series. If you're not familiar with this state management pattern, I'd recommend you to check out this tutorial. The main difference from something like a ChangeNotifier is that BLoCs are separated into 3 core components:

  • States - Their sole purpose is to deliver values (variables) to the widgets.
  • Events - Equivalent to methods inside a ChangeNotifier. These trigger logic inside the BLoC and can optionally carry some raw data (e.g. String from a TextField) to the BLoC.
  • BLoC - NOT A PART OF THE PRESENTATION LAYER!!! But it executes logic based on the incoming events and then it outputs states.

So, if you're not using BLoC, just replace the State and Event classes with a single View Model class of your choice.

With Domain-Driven Design, UI is dumbest part of the app. That's because it's at the boundary of our code and it's totally dependent on the Flutter framework. Its logic is limited to creating "eye candy" for the user. So while animation code does belong into this layer, even things like form validation are NOT done inside the presentation layer.

A rule of thumb is that whenever some logic operates with data that is later on sent to a server or persisted in a local database, that logic has nothing to do in the presentation layer.

Application

This layer is away from all of the outside interfaces of an app. You aren't going to find any UI code, network code, or database code here. Application layer has only one job - orchestrating all of the other layers. No matter where the data originates (user input, real-time Firestore Stream, device location), its first destination is going to be the application layer.

The arrow are Events which are sent from the presentation layer

The role of the application layer is to decide "what to do next" with the data. It doesn't perform any complex business logic, instead, it mostly just makes sure that the user input is validated (by calling things in the domain layer) or it manages subscriptions to infrastructure data Streams (not directly, but by utilizing the dependency inversion principle, more on that later).

If you're not using BLoC, do yourself a favor, and don't put the application logic into View Models. I'd recommend creating reusable, one-purpose UseCase classes. Learn more from the mysterious uncle.

Domain

The domain layer is the pristine center of an app. It is fully self contained and it doesn't depend on any other layers. Domain is not concerned with anything but doing its own job well.

This is the part of an app which doesn't care if you switch from Firebase to a REST API or if you change your mind and you migrate from the Hive database to Moor. Because domain doesn't depend on anything external, changes to such implementation details don't affect it. On the other hand, all the other layers do depend on domain.

So, what exactly goes on inside the domain layer? This is where your business logic lives. We are going to get to everything in detail in the next parts of this series, but everything which is not Flutter/server/device dependent goes into domain. This includes:

  • Validating data and keeping it valid with ValueObjects. For example, instead of using a plain String for the body of a Note, we're going to have a separate class called NoteBody. It will encapsulate a String value and make sure that it's no more than 1000 characters long and that it's not empty.
This kind of validation is usually reserved for the UI. We're, however, going to take advantage of the Dart type system to its full extent. For example, although both EmailAddress and Password encapsulate a String, it will be impossible to pass an EmailAddress to a method expecting a Password argument and vice versa.
  • Transforming data (e.g. make any color fully opaque).
  • Grouping and uniquely identifying data that belongs together through Entity classes (e.g. User or Note entities)
  • Performing complex business logic - this is not necessarily always the case in client Flutter apps, since you should leave complex logic to the server. Although, if you're building a truly serverless ? app, this is where you'd put that logic.
The domain layer is the core of you app. Changes in the other layers don't affect it. However, changes to the domain affect every other layer. This makes sense - you're probably not changing the business logic on a daily basis.

In addition to all this, the domain layer is also the home of Failures. Handling exceptions is a ? experience. They're flying left and right across multiple layers of code. You have to check documentation (even your own one) a million times to know which method throws which exception. Even then, if you come back to your code in a few months, you're going to be again unsure if you handled all exceptional cases.

We want to mitigate this pain with union types! Instead of using the return keyword for "correct" data and the throw keyword when something goes wrong, we're going to have Failure unions. This will also ensure that we'll know about a method's possible failures without checking the documentation. Again, we're going to get to the details in the next parts.

Infrastructure

Much like presentation, this layer is also at the boundary of our app. Although, of course, it's at the "opposite end" and instead of dealing with the user input and visual output, it deals with APIs, Firebase libraries, databases and device sensors.

The infrastructure layer is composed of two parts - low-level data sources and high level repositories. Additionally, this layer holds data transfer objects (DTOs). Let's break it down!

DTOs are classes whose sole purpose is to convert data between entities and value objects from the domain layer and the plain data of the outside world. As you know, only dumb data like String or int can be stored inside Firestore but we don't want this kind of unvalidated data throughout our app. That's precisely why we'll use ValueObjects described above everywhere, except for the infractructure layer. DTOs can also be serialized and deserialized.

Data sources operate at the lowest level. Remote data sources fit JSON response strings gotten from a server into DTOs, and also perform server requests with DTOs converted to JSON. Similarly, local data sources fetch data from a local database or from the device sensors.

Firebase client libraries like cloud_firestore and firebase_auth  do the heavy lifting of data sources for us. That's why we won't create any data sources in this course.

Repositories perform an important task of being the boundary between the domain and application layers and the ugly outside world. It's their job to take DTOs and unruly Exceptions from data sources as their input, and return nice Either<Failure, Entity> as their output. 

If this is the first time you hear about Either, you'll find out more about it in the next parts. Another option is to read this Kotlin documentation if you are impatient ?

If you don't use Firebase Firestore, you'll probably need to cache data locally yourself. In that case, it's the job of the repository to perform the caching logic and orchestrate putting data from the remote data source to the local one.

Code is coming

If you made it through this information-packed article, I salute you. Although we'd all like to immediately start coding, this tendency is precisely what causes spaghetti code and spaghetti architecture to rise ?. Having at least this preliminary information in mind when we start coding in the next part (after setting up Firebase), I'll then guide you through the intricate details as we go. One may say that AOT learning will be complemented by JIT explanations. Yes, I know I should get some fresh air... See you in the next part!

About the author 

Matt Rešetár

Matt is an app developer with a knack for teaching others. Working as a freelancer and most importantly developer educator, he is set on helping other people succeed in their Flutter app development career.

You may also like

  • Greetings, thanks for your super tutorials. Whenever you start a series of tutorials I wonder if you have already written the entire series. and if so and if I invite you a coffee, could I get the whole series?

      • In that case, could you possibly just release the whole code base right now? I learn a lot just by looking at how someone structures something and the way they’ve coded it. Plus it would help out us impatient guys…

        • I will release the whole project repository on Friday. Then there will be a separate repository that will contain code on a per-episode basis. You’ll have to keep in mind though that the “finished” project is not yet fully ironed out in certain areas.

      • 0k, thanks for answering, could you share the full code ?, then you could sell your courses that way. It’s just an idea. Again thanks for your great tutorials

  • Hey Metaj, I was about to email you about getting the full code but saw this chain of comments. You always release the code for the other tutorials, so I assume there is a reason behind not having it available right now. If you could make it public on the ResoCoder github account that would be quite lovely. 🙂 thanks again, I’m really grateful you are putting out such good tutorials!

  • Thank you very much, i really appreciate your work. I deeply studied your tutorial on clean architecture and i’m developing a project with that paradigm. My project involves FirebaseAuth and Firestore so i got a bit stucked. I think i’ll move the project to DDD. Can’t wait to check out the code.

  • Great stuff! I have been following your tutorials and especially your clean architecture. Agree that having top level directories per ferature can be problematic especially for presentation level where some UIs need to present multiple features on same page. Looking forward to your future tutorials!
    By the way, if i would like to optimize my firestore read for the sake of cost, will this architecture handle it in domain/infrastructure level or will I still have to modify my presentation level?

  • Hi Matt,

    Thanks for a a great tutorial once again!

    I have a question about entities: is it possible for them to reference other entities. For example can a Book entity have an author property that references an Author entity? I think I’ve read somewhere that is is a bad idea but I can’t find it anymore and if so what is the reason.

    • Hey Gilles!
      I’m not sure why that should be a bad idea. How else are you going to model relationships?
      Of course, all of the relational DB foreign keys and stuff would be handled in the infrastructure layer, not in the domain entities.

      • tbh i dont agree. i prefer folder structure from clean architecture where you keep your bloc in the same folder as your page is. it’s way easier to find.

  • Hey! The quality of your flutter material is so damn good it makes water stuff come out of my eyes.
    Anyway, I have similar questions as Younes Henni. I think many people will be coming from your Clean Code course. So if you could point out the similarities and differences between the 2 architecture approaches, I would excrete more water stuff! (Water stuff of joy)

  • Hi, is there any reason why you use BLoC instead of States Rebuilder? I just came from your tutorial on that one.

    • I started to dislike states_rebuilder because it’s just too “magical” in my humble opinion. The new state_notifier package looks good though. Regardless of that, we’re going to do this series with BLoC.

      • Hi, Matt, thanks for replying. You meant ‘magical’ because we can’t see how it works inside the code?

  • Hey Matt!

    My concern is about Application layer for those who use BLoC and those who use for instance ChangeNotifier + Provider with MVVM pattern.

    You’re saying:
    “If you’re not using BLoC, do yourself a favor, and don’t put the application logic into View Models. I’d recommend creating reusable, one-purpose UseCase classes. Learn more from the mysterious uncle.”

    Looks like discrimination 🙂

    As far as I know, it’s recommended to have one BLoC class for each page. You get the streams of changes and Events from UI there and decide what’s the next state should be and yield it.
    UI listens to the state changes and rebuilds itself correspondingly. All good.

    So what’s the difference if instead BLoC class we have ChangNotifier class? It gets business logic streams and inputs from UI (by calling its methods), decides what’s the next state should be and yield the states by calling notifyListeners() or emit Stream events.
    UI listens to the state changes (enum for instance) and rebuilds itself correspondingly. All good. Why do bother creating dozens of separate UseCase classes?

    Thanks

    • Hello Kirill,

      The “one BLoC per page” rule is not set in stone and we’re going to have as many as three BLoCs per page, each focused on a different part of dealing with notes.

      I find that BLoCs are nicely composable precisely because they use Events for dispatching actions and they also separate out their data into separate classes (States). In other words, having a BLoC depending on another BLoC is much easier to comprehend than having two ChangeNotifiers be dependent on one another (IMO).

      That’s what I do in client projects too. I’m not using BLoC everywhere and having a use case which depends on 3 different repositories is easy to understand. Having a cluttered view model is much harder to find your way through.

      In the end, feel free to experiment and let me know about your findings ?

  • Hi! do you have a code snippet about how implement a normal API in the infrastructure layer? I mean, with firebase is more easier because is a clientAPI but, if I have my own API? Thanks! a lot

  • Thanks for your great tutorials, I love them and I read them all.
    I have a concern about project folder structure:
    Uncle bob promoting a concept named Screaming Architecture. which says your project folder structure should say what the app is about instead of opening each folder and looking at the classes to find out.
    Quoted from Uncle Bob: ” When you look at the top level directory structure, and the source files in the highest level package; do they scream: Health Care System, or Accounting System, or Inventory Management System? Or do they scream: Rails, or Spring/Hibernate, or ASP?”

    I have to compile and run it in my mind to figure out what it does. What’s worse, I have to expand all the folders to start investigating.

    So how do you think the project structure should improved?

  • I have one question regarding entities vs DTOs. It may happen that there’s this class in the domain which is fine on its own but when we get to reference it saving it or reading later from an external API, how could we handle the ID as it’s not present in the entity but required in the DTO.

  • Hi Matt,
    Great tutorials. I am facing one issue with this architecture. The flutter inspector isn’t working somehow and when I enable “widget select mode” on any screen in the app it shows me the circular loader, which is what we show in the splash screen.

  • I think the DDD architecture is better suited for flutter apps than the TDD since the folder structure is more straightforward and intuitive. But there is no testing at all which is not acceptable for production. Any plans to add testing to this project or do you have any ideas how to add testing. Would love to use DDD over TDD but only with test coverage. Could provide some ideas to add tests to this project – it would be great to have a last part were you add some tests to the project.

    Thank you. Keep up the great work!

  • Hi Matt,

    All my respect, you’re one of the very rare species in the Developers community who is seeking for deep understanding and try to find the best and simple solutions and ofc thanks all your tutorials.

    Anyway, I have some questions:
    1. Do you prefer TDD + Clean to DDD in flutter?
    2. What is your thoughts on some hybrid style development like TDD+BDD+DDD in flutter?
    3. How would you split a complex app to different dart or flutter packages by the used services/features/storyboards (like in Flutter Architecture Samples, though it has only a very simple splitting to only repository or like services in https://github.com/dotnet-architecture/eShopOnContainers).
    4. Also, as flutter was initially built for mobile apps in reactive programming in mind, that needs some different type architectural patterns than the old MVC/MVP/MVVM etc., so like RxVM /w RxCommand (for example achieving to retrieve only read-only data from some underlying cache/db i.e. as some CQRS like solution) or similar. Which one do you recommend or prefer (from like the Flutter Architecture Samples’ examples)?

    Thx in advance.

    Cheers,

    Pal

  • Hi Matt,
    Thank you very much for the amazing tutorials.
    About entities : would you use an entity as a value object of another entity ?
    For example if one property of note is “owner” and you want to fill it with a user, would you use directly the user entity or would you create a new value object ?
    Thank you very much

  • Hi Matt,
    can you explain in more detail why this new folder structure ist better than the one in the “Clean Code” tutorial. I thinks it is much easier to navigate throught the code if the project contains the feature-folders first and then the layers underneath.

    Thank you for the great tutorials!

  • Hi, I’m following your flutter-firebase-DDD course these days. But I can not complete it because I can not go to the written tutorials in the course. The page loads only for the 9th tutorial of your blog and I can not find the rest of the written tutorials in your blog. If you would like to share the link to those 32 written tutorials I would appreciate it.

  • Thank you for the awesome tutorials. I have a question. Why is your BLoc considered a use case on DDD but not on Clean Architecture. In clean architecture your bloc uses a Use Case.

  • Clean Architecture or this one? for the project currently on firebase but will soon be migrated to a custom backend. TIA

  • Thank you very much Matt, this is such a great source of information for Flutter development!

    I don’t really agree on what you said about serverless when talking about the Domain Layer and handling complex business logic: “Although, if you’re building a truly serverless ? app, this is where you’d put that logic.”
    I think that frontend development should not include actual business logic, otherwise you are building some kind of monolith. In serverless, business logic is typically on lambdas, who can scalate and be managed independently from the frontend technologies.

    Best regards!

    • Hi Guillermo,
      If you don’t use lambdas or other cloud functions in general, and you just rely on Firebase Firestore or AppWrite DataStore, then what he said is right!

      Not all serverless mean lamdas (in fact that is just an overhyped marketing slogan since lamdas actually run on servers outside of your maintenance, and you still have to technically write the business logic, whereas apps that rely only on firestores/datastores have the business logic in-app. Datastores only act as remote infrastructures rather than local, firestores act as both remote and local, and they are both used for data persistence only. In actual sense, this is what serverless means because none of your code/business logic is being run in a remote machine on the cloud or should I say, “in a remote server you do not maintain’?).

      All of these approaches have their use cases, for example, if you don’t want your code to be reversed engineered make sure you go with lamdas serverful ha ha. If reverse engineering does not bother you, go with in-app serverless approach.

  • When you make a tutorial, I feel that the tutorial should cover just that one topic. Your tutorial has a steep understanding curve because you have mixed in several topics into one tutorial, making it useless for me at least.

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >