Build a clean “Game of life” app in Flutter using Hexagonal Architecture and TDD: Part 1
In this series, I show how I develop a “Conway’s Game of life” application in Flutter. This is the Part 1 where I mainly talk about the Hexagonal Architecture and the global structure and features of the App.
Through the articles, I will show :
- What is my process from start to finish ;
- How I structure the application with the Hexagonal Architecture ;
- How I write business logic code based on the TDD approach (see Part 2)
- How I wired everything together.
The project : “Conway’s game of life”
If you don’t know what is this game, you can find all the information about it on Wikipedia.
Basically, the game of life is a grid of cells, each of which is either dead (empty) or alive (filled).
Given a initial state, the cell state (I.e. dead or alive) evolves by itself based on the following rules :
- Any live cell with fewer than two live neighbors dies, as if by underpopulation.
- Any live cell with two or three live neighbors lives on to the next generation.
- Any live cell with more than three live neighbors dies, as if by overpopulation.
- Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
Here is an example of what it looks like :
The features
At this stage, I have an idea and have some thought of how it should behave. It is therefore the perfect moment to write those thoughts down in plain English.
User Stories
In practice, I write down some basic user stories like these :
As an app user :
- I want to have an initial game loaded upon launch
- I can play the game
- I can pause the game
- I can reset the game to its initial state
- I can see the game state frame by frame
- I can increase/decrease the speed of the game
Given multiple games are available
- I can see the list of available games
- I can select one of those game (and play with it)
Given I’m super creative
- I can to create a game (and play with it)
UI elements and Actions
Based on those user stories, I go one step further by defining what would be the UI elements and actions needed to cover the user stories.
This allows me to have a basic but good understanding of what I have to implement somehow in the app.
The Hexagonal Architecture
Now is a good time to discuss briefly about the Hexagonal Architecture (only briefly since it is already well explained here, here, here, here and also here).
The main idea of the Hexagonal Architecture consist of :
- Isolating the business logic in the “hexagon” (“the core”)
- Making it totally independent from the outside (and ignorant of it)
This architecture has several benefits. This will indeed :
- Ease the testing
- Ease the maintainability
- Ease the scalability
- Allow to defer implementation decision
- Ease the technology swapping
As every article on Hexagonal Architecture provides an illustration of it as interpreted by the author. Here’s mine :
There are 3 big parts in the Hexagonal Architecture.
The Primary Actors/Drivers (the left side)
They are basically the users of the business logic isolated in the “hexagon”. They triggers some actions/services provided by the core. Without those users, nothing should happen.
The Secondary Actors/Drivers (the right side)
They are the repositories or other services on which relies the core. The core is therefore the user of those services.
The Core application (the “hexagon”)
This is where the business logic is isolated to be independent from the outside. To allow some interactions, the “hexagon” provides some “ports” to the attention of the outside world.
Personally, I like to rename those “ports” depending on the audience :
The “Use cases” : Those are the “ports” opened for the primary actors, basically the services that the core provides to the users.
The “Interface” : Those are the “ports” available for the secondary actors. In practice, the “hexagon” provides an interface (I.e. an abstract class in Dart) that will have to be implemented and provided to the “hexagon”. The key point here is that this is the core that defines the interface (I.e how the secondary actors should behave), and not the opposite.
Around those ports are the “Adapters” that are essentially the implementation of the ports, e.g. :
- On the left side, it could be a ViewModel class that uses the services provided by the “hexagon” and make the connection with the View ;
- On the right side it could be a Repository Adapter that implements the Repository Interface provided by the “hexagon” and make the connection with a chosen database.
Last but not least, on the very inside is the “Entities” which are basically every classes, functions, enums… that you code to make things work.
Hence…
By “simply” providing services to the users and by dictating the behavior of the secondary actors, the “hexagon” remains totally independent from the outside world. For instance :
- You want to switch from MySQL to PostgreSQL ? You just have to code a new implementation of the Repository Interface provided by the “hexagon” → No change is required inside the “hexagon” ;
- You want a Flutter Web app next to your Flutter Mobile app or you want to build a rest api for which you need the business logic of the “hexagon”? You still just have to call the services (use cases) provided by the “hexagon” → No change is required inside the “hexagon”.
Great, isn’t it ?
The “Let’s put the pieces together”
Based on what the app should do, let’s make the link with the Hexagon Architecture.
Primary Actor
Obviously, the primary actor is here the Flutter Mobile App. The UI elements will be the view, while the ViewModel will make the connection between the View and the “hexagon” services. The ViewModel will hold the state of the app.
Core
Through the actions written down earlier, we can extract two main business logics :
- The Game logic (that should implement the rules of the game of life) ;
- The Timer logic (that should do something every given interval).
Each one of these can be totally independent from everything else and be isolated in a “hexagon”. However, since there is already a built-in Timer in Dart (async package), we will use it as such and focus on the Game logic.
Use cases
What will be the use cases of the “Game hexagon” ? For which reasons the primary actors would need it ?
Easy. They need the “hexagon” to :
- Get a random game ;
- Get a list of available game ;
- Generate the next state of a game based on the rules of the game of life.
Those are the use cases.
Interface
Then, what will the “hexagon” need in order to respond to those use cases ?
Since the “hexagon” needs to provide some predefined games, it surely needs a Game repository.
This will be the interface.
Secondary Actor
As secondary actor, we will only have an implementation of the Game repository. For this project, we will just implement an in memory database.
Summary
As summary, here is an illustration of the overall architecture of the app, using the Hexagonal Architecture.
Next up
Now that I have a clear idea on how the app will be structure, it’s time to write some code.
In the next article of the series, I will show how I use TDD to code the Business Logic inside the “Game hexagon”. You can find the Part 2 here.