Factory
Factory copied to clipboard
Microservice concept using Factory
Hi @hmlongco,
I've read your articles on SwiftUI architecture and they're inspiring. I notice there's a difference of Service's usage in your old articles and those in the demo project of Builder / Factory. I wonder if I understand concept of Service correctly.
From your article in 2019 SwiftUI Microservices, you define Service as:
Services, on the other hand, are created and shared among multiple views and components in our SwiftUI application by injecting them into the application environment at some level of the view hierarchy for use by elements at a lower level. They persist for as long as that level persists.
This means a view can depend on any number of services, which are small observable objects with some published properties and methods that mutate their properties.
Let's use an example SwiftUI component to demonstrate. Imagine we're building a calendar app that has a sidebar, which contains a small calendar at the top and some tabs to navigate below it, similar to Google calendar.

User can select a date from the calendar to display events on that date in the main screen. In the sidebar we want to get today, selected date and selected tab from services. We don't want to put them in single app state because making one single change to application state — say, changing selected date — and now every single view tree in the application needs to be walked and checked for changes. Hence I split them into 3 services.
p.s. I don't want to use TodayService and SelectedDateService inside Calendar component directly by marking them @EnvrionmentObject because Calendar may be used elsewhere which should not be affected by selected date from outside e.g. date picker for user to choose event's date when user creates an event, I don't want choosing a date in the date picker will change the selected date in the inbox view. Hence I prefer to make Calendar a "dumb" / "presentational" component.
class CurrentTabService: ObservableObject {
enum Tab {
case inbox
}
@Published var tab: Tab = .inbox
}
class TodayService: ObservableObject {
@Published var date: Date = .init()
}
class SelectedDateService: ObservableObject {
@Published var date: Date = .init()
}
struct SystemServices: ViewModifier {
private static var currentTabService: CurrentTabService = .init()
private static var selectedDateService: SelectedDateService = .init()
private static var todayService: TodayService = .init()
func body(content: Content) -> some View {
content
.environmentObject(Self.currentTabService)
.environmentObject(Self.selectedDateService)
.environmentObject(Self.todayService)
}
}
struct Sidebar: View {
@EnvironmentObject var currentTab: CurrentTabService
@EnvironmentObject var today: TodayService
@EnvironmentObject var selectedDate: SelectedDateService
var body: some View {
VStack {
Calendar(today: $today.date, selectedDate: $selectedDate.date)
SidebarItem("Inbox", currentTab: $currentTab.tab)
...
}
}
}
}
struct AppView: View {
var body: some View {
HStack {
Sidebar()
MainScreen()
}
.systemServices()
}
}
Then from your later article in March SwiftUI: Choosing an Application Architecture, you say that:
With a View Model, we want to move as much of the logic out of the view as possible, leaving behind code that’s dead simple to understand. That’s a good example of separation of concerns. We put our business logic on one side of the fence, and all of our view presentation and layout on the other.
Using our previous example, it means we should have a SidebarViewModel to "put our business logic on one side of the fence, and all of our view presentation and layout on the other." We will need to inject services into SidebarViewModel that control current tab, selected date and today. It then becomes the mediator between Sidebar view and those services e.g. get the values from them, expose methods to modify those values, perhaps using Factory.
struct Sidebar: View {
@StateObject var viewModel: SidebarViewModel = .init()
var body: some View {
VStack {
Calendar(today: $vm.today, selectedDate: $vm.selectedDate)
SidebarItem("Inbox", currentTab: $vm.currentTab)
...
}
}
}
}
Which way would you choose if you were me given that we can use Factory or Resolver for DI nowadays? Another question is how should I design the services using Factory, since I want to store values e.g. today, selected date and current tab in those services, and then make them publishable inside the SidebarViewModel? Should I put them altogether in 1 service say AppState or keep them split in 3 services when I use Factory?
Thanks, Joseph
Wow. Yeah, I've the word "service" in various contexts over the years. The oldest use really came from Resolver, which borrowed the name from Swinject. From the DI perspective, every dependency was something that provided some sort of "service" to the containing object. (If you look at the Resolver code you'll see the word everywhere.)
In classic MVVM, a view depends on a ViewModel, which in turn usually manages and depends on one or more services (network, etc.) to do its job. Further, a ViewModel and a View are usually tightly coupled. ViewModels are smart and testable, Views are dumb and usually can't be tested.
The micro-service concept came from the idea that sometimes our SwiftUI views need shared state throughout the application. Since that state was used for multiple views, it wasn't really a ViewModel, so I coined the idea of a micro-service.
Finally the whole thing is somewhat convoluted in SwiftUI which depends on whether or not we're using EnvironmentObject or DI, since only ObservableObjects can trigger state changes, and even, as I maintained in yet another article not every view needs or warrants a ViewModel. And it doesn't help when Apple adds to the fray with wrappers like @FetchRequest or @SceneStorage that only work within a view.
Bottom line is that I've yet to develop a grand unified theory for SwiftUI Architecture, and that (as may be obvious) my feelings on such have evolved over time.