The Builder-Feature-Graph Architecture (the BFG Architecture, BFGA for short) is a general approach to iOS app architecture for complex large-scale apps.
This approach focuses on creational code and its composition, not on a specific framework or pattern like MVP, MVVM, RIBs, TCA etc.
- Origins of the BFG Architecture
- A General Guide to Architecture and App Feature Development
- Builders
- Feature Graph
- Communication between features
- The BFGA example
The BFGA has several sources:
- RIBs mobile architecture framework by Uber. A concept of Builder has been taken from it.
- Graph-module approach by Yandex. A concept of Graph is from this approach.
- Other common architectures, design patterns, SOLID principles, MVC, MVP, VIPER, Undirectional Data Flow etc.
An app is implemented as a feature tree. Feature is an entity that represents product functionality or carries out an important role in a product.
A feature can use services or other types of dependencies. The distinction between features and dependencies can be subjective.
Do not use global objects, variables and any implicit dependencies.
A feature is instantiated by a builder. In general, a builder looks like:
protocol FeatureBuilder {
func build() -> FeatureGraph
}
final class FeatureBuilderImpl: FeatureBuilder {
init(/* external dependencies */) {
// ...
}
func build() -> FeatureGraph {
// ...
return FeatureGraphImpl(/* feature dependencies */)
}
}
A goal of a builder is to call a feature graph initializer with required dependencies. Every feature has its own dependency scope. The feature gets external dependencies from the initializer of its builder. Inner dependencies are defined inside the scope of the builder.
A builder of a feature is created by a builder of a parent feature. A task of the parent feature is to choose an implementation of the builder of the child feature and to pass dependencies to it. These features do not have to have other logical connections between each other.
To include a feature in a component of a project you need to pass a builder of the feature to an initializer of the component.
In short, a feature graph is a DI-contaner having an output of functionality that can be used in a project.
A video from Yandex where a concept of feature graph is explained.
protocol AppGraph {
var firstScreen: UIViewController { get }
var pushNotificationHandler: AppPushNotificationHandler { get }
}
protocol SomeGraph {
var view: UIView { get }
var model: SomeModel { get }
}
A feature graph is a general concept for a feature having two or more outputs. If a feature is represented as a single object then the feature graph is redundant. For example, if a feature consists of an only screen then a builder should look like this:
protocol ViewControllerBuilder {
func build() -> UIViewController
}
final class SomeScreenBuilder: ViewControllerBuilder {
// ...
}
If two features relate to each other in a child-parent way then you can make a parent to be a delegate of a child:
protocol FeatureListener: AnyObject {
func onSomeDataChanged(_ data: SomeData)
}
Communication between 'distant' features can be arranged as follows:
-
NotificationCenter
or strictly-typed wrappers around it. -
Unidirectional Data Flow frameworks.
Personal Dictionary is a project that is based on the BFGA approach. A feature tree of the app is
B
- builder, G
- graph, R
- router, V
- view, VC
- ViewController, MVVM
- UI pattern, UDF
- Unidirectional Data Flow, D
- domain.
Discussion of architectural problems and issues is encouraged.
If you have questions about this approach or have noticed any mistakes, please, inform about it.