My review of Mobile system design
In this book, the author tries to define System Design as designing a technical solution to satisfy business requirements. So, the book focuses on the steps we should follow to solve a design system problem.
This book is good for junior/middle engineers since the author describes every step in detail. If you are senior+, I think you can skim some chapters related to testing, dependency injection, and UI.
In Designing systems, we also need to consider non-business requirements, such as performance and security. However, so far, the author has not mentioned this. The book is not finalised, and more chapters are coming. I hope the book will mention these in the next chapters.
Key points
1. Understand the problem better before starting to code.
Problems
A lot of engineers, including myself some time, start coding without understanding the requirements carefully. They often start doing the UI part, then integrate with the API, … The problem is that later they discover issues with the requirements or API, and they have to rewrite the code.
It wastes a lot of time, and that’s one of the main reasons why they miss the deadline.
Take away from the book
So, the first step is to understand the requirements carefully. We can do that by:
- Break down the requirements into smaller tasks, it could be feature tasks and technical tasks.
- Ask questions, challenge the requirements, and try to find the edge cases.
- Ask functionality-related questions: Try to come up with ways the feature might work differently from intended
- Ex: If the user presses “reset all”, would the user get a warning? Some sort of alert perhaps?
- Do we need to support deeplink/websocket? …
- Challenge the design: Try to find the edge cases, the problems with the design.
- What is the design for loading/error/empty cases?
- What if the text is too long? What if BE doesn’t return data for that field? …
- Aligning with backend engineers:
- Asking BE to provide API document. Check which field is optional/require
- Which API do we need to call to get the data? …
- You can also sketch out a landscape, which is a collection of domains and components that will be required to make our feature work. It helps all the stakeholders to understand the big picture of the feature.
2. Holistic-Driven Development
Take away from the book
1. What is Holistic (top-down approach)?
- Create interface and placeholder implementation for all the components (from top to bottom), and integrate them to create a running application, … Then slowly replace them with the real implementation.
2. Benefits
- We can defer critical decisions (such as Database, Networking calls, Caching, …)
- Easier to work in parallel with other team members
- “In an interview, it will be a similar approach. People will want to see how you think about the interplay between all systems. They want to see how data flows and how all parts connect together into a complete system”
3. Process
- API design:
- Desgin domain model
- Normally in workflow, we have BE team to provide the API document. But in the interview, we need to do it ourselves
- Create interfaces for components, and create a placeholder implementation for those components
- Ex: For UseCase, at this stage, we can hardcode some response data instead of making a real network call.
- Integrate those placeholder components to create a running flow
- One by one, replace placeholders with real implementations one abstraction layer at a time
Note:
- Work on the UI later, Favor integration with backend over polishing UI to find integration issues early
- We delay writing tests because code at this stage is volatile
3. Testing
In the next chapters, the author mentioned some foundation knowledge of testing, unit-test vs system testing, and how to do testing.
This chapter is basically for junior/middle, so I will not add it here.
4. Dependency Injection
Take away from the book
-
Should use DI for 3rd party libraries
-
Avoid using Singleton (There are many articles that mention about why, so I will not mention it here)
-
Avoid ABC Problem
- In the book, the author introduces the concept “ABC problem”. It means that the A component should not know about the C’s dependencies. We easy to face this problem if the A init the instance of B directly.
- To avoid this problem, we can use Composition Root technique.
- Throughout the rest of the app, avoid breaking the ABC rule to keep types decoupled.
-
Lazy initialization
- When using DI container, we init the B component from outside, then inject the B component to A via A’s init function. However, in some cases, the B component can’t be init in the first place.
- To solve this problem, we can A lazy dependency is a class that returns a dependency
- Lazy initialization approach:
class B { let selectedInput: Int init(selectedInput: Int) { self.selectedInput = selectedInput } } class BFactory { func initB(from selectedInput: Int) { return B(selectedInput: selectedInput) } } class A { let bFactory: BFactory init(bFactory: BFactory) { self.bFactory = bFactory } func performAction(selectedInput: Int) { let bInstance = bFactory.initB(from: selectedInput) } }
-
DI across modules:
- Try to let a module set up its own hierarchy. You can use static functions for this that can easily be moved around.
- By using a static function, a class instance becomes unaware of dependencies, thus avoiding the ABC problem.
If you middle/junior engineers, I highly recommend you to read the book Dependency Injection Principles, Practices, and Patterns
5. UI Architecture
In these chapters, the author focuses on implement the UI components
Take away from the book
1. Choosing UI Architecture
- Focusing on separate business display logic from UI to testing and Team familiarity
2. Develop UI components
-
3 types of view:
- View primitive: Simple view, unaware of the feature they are used in
- View components: These are smarter views, such as a map view, or a navigation bar. They have more logic and/or data bindings. But just like view primitives, they do not know about the feature(s) they’re used in.
- Feature views: They have logic and/or data bindings, and are connected to business logic.
-
Develpoing primitive view (reusable views)
-
Naming a view:
Problems:
One very common “mistake” is that developers give a type (such as a view) a name that’s too specific => Less reusable view
Advice:
We should make it reusable view if possible, later we can move it to share UI module.- Should not know how it’s used, dont be too specific
- Should not know how it’s implemented
- Don’t name a view after its styling: Shouln’t call BubbleView, but instead can call CalloutView (more generic).
Think about what a view is as opposed to how it’s used
-
Trade off between easy to change vs effort to change if it happen
- If a view is used only once, then, as a heuristic, make it reusable only when it takes low effort and if you can keep the complexity low.
- If it takes low effort to make a view reusable, and if the complexity stays low, then consider make a view reusable, even for a single purpose. Because the risk remains low.
- If you introduce a simple view primitive, it can often directly move to a UI library, since they commonly are reusable with no extra effort.
-
-
Prefer making more complex views from smaller, simpler views.
-
Composition over smart views:
- Instead of trying to create a reusable view and having lot’s of options to config it (smart view), we can create a feature view by compose primitive views.
3. UI Binding
This section is about how to bind data to UI by using SwiftUI/UIKit.