Video #110: Designing Dependencies: The Problem
Episode: Video #110 Date: Jul 27, 2020 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep110-designing-dependencies-the-problem

Description
Let’s take a moment to properly define what a dependency is and understand why they add so much complexity to our code. We will begin building a moderately complex application with three dependencies, and see how it complicates development, and what we can do about it.
Video
Cloudflare Stream video ID: 98413367a061c4f92358394e5c97be0a Local file: video_110_designing-dependencies-the-problem.mp4 *(download with --video 110)*
References
- Discussions
- MetaWeather
- How to Control the World
- Protocol-Oriented Programming in Swift
- Protocol Oriented Programming is Not a Silver Bullet
- 0110-designing-dependencies-pt1
- Brandon Williams
- Stephen Celis
- Mastodon
- GitHub
- CC BY-NC-SA 4.0
- source code
- MIT License
Transcript
— 0:05
Today we are going to begin a series of episodes on “designing dependencies.” We’ve talked about dependencies quite a bit on Point-Free in the past, starting over 2 years ago when we introduced the idea of dependency injection and how it can be made quite easy by packaging all of your dependencies up into one mega struct. And then earlier this year we showed how to bake that notion of dependency injection directly into the Composable Architecture so that each feature of your application precisely describes exactly what dependencies it needs to do its job, and then as you decompose your app into smaller and smaller components you simply describe how to slice up the entire world of dependencies into just the subset a particular component needs.
— 0:44
There are some really powerful ideas in those episodes, but what we haven’t done yet is discuss exactly how to design dependencies when you are writing code in this style. We’ve shown a few hints of how to do this, for example we strongly recommend forgoing classes, protocols and manager objects, and instead use simple data types. It can be a little uncomfortable to disband with those tools since they’ve been in our tool belt for so long, but there is a lot of power in doing so. So, we want to spend a little bit more time with this style of dependencies and show how to wrap some pretty complex functionality in simple data types instead of using protocols, and we’ll show that this unlocks some truly amazing functionality. What is a dependency?
— 1:40
Let’s start with the basics. What is a dependency and why do we want to think about them? After all, we’re just trying to make an app here. Don’t we just wanna fire up Xcode, create a new view, and start build out a view hierarchy with nice styles and animations??
— 1:54
Well, where do you get the data that powers that view? At some point you probably want to make a network request to an external API. Or you may call out to an Apple framework, like if you wanted to get your user’s current location by using Core Location. Or perhaps you are using some functionality from a third party library, such as Alamofire for putting a nice interface to make API calls, or Starscream for handling web sockets, or ffmpeg for processing video. All of these things are dependencies because your code needs them in order for it to do its job.
— 2:25
And that definition may seem innocent enough, but when left unchecked dependencies can wreak havoc on the health and maintainability of your code base. Here are a few examples of how that can happen:
— 2:38
Dependencies can increase compile times. If the target you are building has a lot of dependencies, then the first time you build that target you will incur the cost of building every dependency, such as Alamofire, Starscream, and maybe dozens of other little libraries. Once the initial build is made you may be able to go quite awhile without building again, well, that is until you want to change branches to check out someone else’s work, or merge the main branch into yours, or if you need to do a clean because something is wonky in your project. So, even though you get to skip a lot of long builds with incremental compilation, there will still many times that isn’t possible.
— 3:29
Dependencies cause stress on build tools and Apple’s tools. Playgrounds and Xcode previews may even stop working when certain dependencies are introduced, and these include Apple frameworks. For example, Core Audio, Core Location, Core Motion, Store Kit, and more do not work in these build tools.
— 3:48
Dependencies are difficult to test. Most dependencies are not built with testing in mind, and if you naively introduce their code into your code you will have a bunch of black boxes of functionality sitting in your application that you have no way of running in a reproducible environment. This not only means it’s difficult to unit test in an automated way, but also means it’s difficult to reproduce certain situations when running on an actual device.
— 4:34
And that’s just the beginning. There are even more things to think about when it comes to dependencies.
— 4:40
So, simply put: you may have brought in a dependency because it solved a problem you were having, but each dependency also adds a new layer of complexity to your application.
— 4:52
Lucky for us, the complexities we just described above are not a part of the core problem domain we are dealing with, but rather they are just an secondary side effect that happens when we introduce dependencies without forethought. This principle is sometimes known as “essential” complexity versus “accidental” complexity. In particular, using Core Location in your application brings in a lot of essential complexity into your application because getting location permissions and requesting location coordinates is a complex state machine that is fraught with potential failure, but that is just the reality of location services on mobile devices. However, the side effect of playgrounds and Xcode previews all of a sudden no longer working has nothing to do with location services, and so this is accidental complexity, and we can actually do something about this kind of complexity. An app with dependencies
— 6:02
Let’s take a look at the app we are going to slowly build in order to explore the idea of designing dependencies. It’s a weather app with a few interesting features. First of all, you give the app access to your location, and if you do that it will try to use your current location to find your weather.
— 6:19
And now we have some weather results. We also added a feature to handle what happens when the internet connection of the device changes. If we turn off internet right now we’ll see the results clear and a little message shows. Then if we turn internet back on…well, the message is still there, but this is just a bug that happens in the iOS simulator. If we kill the app and relaunch it, though, we’ll be in a connected state and weather results will come in.
— 7:26
This may seem like a simple application, but it’s actually quite complex. First, dealing with location stuff means we are integrating with Apple’s Core Location framework, and it’s a quite complex state machine that can require going through multiple steps of state. Then we have the weather requests, which interact with an external API, which could be up or down. And finally we are listening for connectivity and using that information to drastically change the UI.
— 8:15
These are 3 different dependencies all feeding their events and changes into our application, and they are all interacting with each other. Like it or not, this is already a super complex application! An API dependency
— 8:50
So let’s get started building it.
— 8:53
We’re going to start by pasting in some basic infrastructure to give us some momentum. Most of this is standard SwiftUI stuff and we’re not focusing our energy on explaining the best ways to create SwiftUI views right now. So we can start by pasting in a basic view model that we will soon power our entire application: class AppViewModel: ObservableObject { @Published var isConnected = true init(isConnected: Bool = true) { self.isConnected = isConnected } }
— 9:22
We are specifically not using the Composable Architecture for this demo, because although we love building applications in that style, the problem of designing dependencies goes well beyond a particular architecture. What we are going to talk about today is important to know no matter what kind of architecture you are using, including if you are building a plain, vanilla SwiftUI or UIKit application.
— 9:44
Let’s also paste in a big chunk of view code with some basic scaffolding in place: struct ContentView: View { @ObservedObject var viewModel: AppViewModel var body: some View { NavigationView { ZStack(alignment: .bottom) { ZStack(alignment: .bottomTrailing) { List { EmptyView() } Button( action: { } ) { Image(systemName: "location.fill") .foregroundColor(.white) .frame(width: 60, height: 60) } .background(Color.black) .clipShape(Circle()) .padding() } if !self.viewModel.isConnected { HStack { Image(systemName: "exclamationmark.octagon.fill") Text("Not connected to internet") } .foregroundColor(.white) .padding() .background(Color.red) } } .navigationBarTitle("Weather") } } }
— 9:52
And in order to get things building we need to pass a view model wherever we construct a ContentView . This includes the preview provider: struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(viewModel: AppViewModel()) } }
— 10:03
And the scene delegate: func scene( _ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions ) { let contentView = ContentView(viewModel: AppViewModel())
— 10:09
This is giving us the basics of a list view with a location button hovering over it in the bottom right, as well as a little warning view hovering over the bottom that only shows when we are not connected.
— 10:37
And we can even flip the isConnected field to false to see what the warning looks like: ContentView(viewModel: AppViewModel(isConnected: false))
— 10:52
The first real piece of functionality we are going to build for our app is the ability to load some weather data from an API. We are going to use an API called MetaWeather , which offers a fully public API for weather data. For example, if we hit the following endpoint: $ curl -Ls https://www.metaweather.com/api/location/2459115
— 11:19
We get a bunch of JSON back. To pretty print this we can pipe the result into Python from the command line: $ curl -Ls https://www.metaweather.com/api/location/2459115 \ | python -m json.tool
— 11:31
And now we see: { "consolidated_weather": [ { "air_pressure": 1013.5, "applicable_date": "2020-07-05", "created": "2020-07-06T00:17:43.396772Z", "humidity": 51, "id": 6719613838557184, "max_temp": 33.215, "min_temp": 24.740000000000002, "predictability": 70, "the_temp": 33.085, "visibility": 14.985609043187782, "weather_state_abbr": "lc", "weather_state_name": "Light Cloud", "wind_direction": 218.72620966328083, "wind_direction_compass": "SW", "wind_speed": 3.1470878509557516 }, … ], … }
— 11:46
In order to load this JSON into a Swift program we need to create data types that represent this response from the API. We can do this by creating some structs that mimic the structure of this JSON: import Foundation struct WeatherResponse: Decodable, Equatable { var consolidatedWeather: [ConsolidatedWeather] struct ConsolidatedWeather: Decodable, Equatable { var applicableDate: Date var id: Int var maxTemp: Double var minTemp: Double var theTemp: Double } }
— 12:20
And a JSON decoder configured to deal with some edge cases, like the date format and the fact that the API uses snake case: private let weatherJsonDecoder: JSONDecoder = { let jsonDecoder = JSONDecoder() let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd" jsonDecoder.dateDecodingStrategy = .formatted(formatter) jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase return jsonDecoder }()
— 12:57
With our model defined we can add some new state to our view model that represents the weather that has been loaded so far: class AppViewModel: ObservableObject { @Published var weatherResults: [WeatherResponse.ConsolidatedWeather] = [] … }
— 13:15
And then we need find a spot in the view model to actually start loading the weather. Ultimately we want this driven off of finding the user’s current location, but it’s going to take time to get there, so for now let’s just kick off a weather request as soon as the view model is created, and we’ll just load the weather for New York City: init(isConnected: Bool = true) { … URLSession.shared .dataTaskPublisher( for: URL( string: "https://www.metaweather.com/api/location/2459115" )! ) .sink( receiveCompletion: <#((Subscribers.Completion<URLError>) -> Void)##((Subscribers.Completion<URLError>) -> Void)##(Subscribers.Completion<URLError>) -> Void#>, receiveValue: <#(((data: Data, response: URLResponse)) -> Void)##(((data: Data, response: URLResponse)) -> Void)##((data: Data, response: URLResponse)) -> Void#> ) }
— 13:58
And now we can implement these two closures in order to handle the completion event, which could be an error, or the value event which will deliver some data and a URLResponse to us.
— 14:09
For now we aren’t going to handle any errors, so we can stub in an empty closure: receiveCompletion: { _ in },
— 14:19
For the receiveValue closure we can decode the JSON into the WeatherResponse type, which is a throwing function and so we need to work around that a bit: receiveValue: { [weak self] data in self?.weatherResults = (try? weatherJsonDecoder.decode(WeatherResponse.self, from: data)) ?.consolidatedWeather ?? [] }
— 15:21
We also need to store the cancellable sink returns, otherwise the request will be cancelled immediately. So let’s store that in the view model: import Combine … class AppViewModel: ObservableObject { … private var weatherRequestCancellable: AnyCancellable? init(isConnected: Bool = true) { … self.weatherRequestCancellable = URLSession.shared .dataTaskPublisher( for: URL( string: "https://www.metaweather.com/api/location/2459115" )! ) .sink( … ) } }
— 16:03
We can clean this up, though, by leveraging Combine operators to do the decoding for us so that the sink closures become nice and succinct: self.weatherRequestCancellable = URLSession.shared .dataTaskPublisher( for: URL( string: "https://www.metaweather.com/api/location/2459115" )! ) .map { data, _ in data } .decode(type: WeatherResponse.self, decoder: weatherJsonDecoder) .sink( receiveCompletion: { _ in }, receiveValue: { [weak self] in self?.weatherResults = $0.consolidatedWeather } )
— 17:01
This isn’t quite right yet, and the problem is very subtle. The receiveValue closure will be executed on a background thread, but the observable object’s @Published publishers must be updated from the main thread. We can ensure this by leveraging Combine’s receive(on:) operator. .receive(on: DispatchQueue.main) .sink( … )
— 17:28
In order to see these results in the SwiftUI preview we need to add some rows to our list. We can use the ForEach view to iterate over the list of weather results and render them: ForEach(self.viewModel.weatherResults, id: \.id) { weather in VStack(alignment: .leading) { Text("Current temp: \(weather.theTemp, specifier: "%.1f")°C") Text("Max temp: \(weather.maxTemp, specifier: "%.1f")°C") Text("Min temp: \(weather.minTemp, specifier: "%.1f")°C") } }
— 17:50
This now shows the current temperature, as well as the max and mins for the day, and when we run it in the preview, we see a list of weather results.
— 18:26
But the row doesn’t show us the day. To get that we need to use a date formatter, so let’s quickly paste one in that will give us the day of the week: let dayOfWeekFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "EEEE" return formatter }()
— 18:38
And then we can use it like so: Text(dayOfWeekFormatter.string(from: weather.applicableDate).capitalized) .font(.title)
— 18:57
And just like that we have a super basic weather app. It’s honestly kind of amazing how quickly we were able to get this working. SwiftUI is incredibly powerful. Controlling an API
— 19:08
However, what’s not so amazing is the fact that with every editor keystroke we are reloading the preview, which means we are making a whole new API request. Just while trying to sketch out this UI we probably made a dozen or more requests, and so not only do we run the risk of flooding our server with pointless requests, but we are also dependent on the availability of the server in the first place. And worse, we can only show whatever the server gives us back, but we may want to preview our UI in other states, like when lots of weather results come back, or when temperatures are in the negatives, just so that we can be certain that we are displaying things correctly.
— 20:19
And so we have now come face-to-face with our first dependency in this app. This is code that needs to speak to something that we do not control, in particular some code living far away on a server somewhere, and that is causing a lot of complexity to creep into the application. We need an internet connection and need to make live API requests just to get data for this screen, and we are at the mercy of the logic on the server to give us the data.
— 20:48
Things are already getting quite complex for something as simple as an API request, and it only gets worse as the dependency gets more complicated, as we will soon see.
— 20:56
So, how do we fix this?
— 20:58
Well, we can stop doing API requests in a hidden, implicit way inside the view model, and instead have the view model demand that whoever constructs it must pass in all the dependencies it needs to do its job.
— 21:11
Perhaps the most accepted way to do this in the iOS community is to leverage “protocol-oriented programming” to put an abstraction in front of the dependency so that we can pass anything conforming to that interface and the view model can just use it however it wants. We could pass in a conformance that actually makes network requests, or one that synchronously returns mocked data, and the view model would be none the wiser.
— 21:38
So we may start with a protocol like this: protocol WeatherClientProtocol { func weather() -> AnyPublisher<WeatherResponse, Error> }
— 22:24
And then we could create a concrete type to conform to this protocol, and its implementation will basically be what we currently have in the init : struct WeatherClient: WeatherClientProtocol { func weather() -> AnyPublisher<WeatherResponse, Error> { URLSession.shared .dataTaskPublisher( for: URL( string: "https://www.metaweather.com/api/location/2459115" )! ) .map { data, _ in data } .decode(type: WeatherResponse.self, decoder: jsonDecoder) .eraseToAnyPublisher() } }
— 22:54
And then we can force the view model to be constructed with one of these WeatherClientProtocol conformances, which for simplicity we could even default to the live implementation: init( isConnected: Bool = true, weatherClient: WeatherClientProtocol = WeatherClient() ) { … }
— 23:14
And finally we can actually use that client in our code: init( isConnected: Bool = true, weatherClient: WeatherClientProtocol = WeatherClient() ) { … self.weatherRequestCancellable = weatherClient.weather() .sink( receiveCompletion: { _ in }, receiveValue: { response in DispatchQueue.main.async { self.weatherResults = response.consolidatedWeather } } ) }
— 23:36
But so far we haven’t really flexed the muscles of this abstraction. All we’ve done is moved some code from the view model to another type, and everything basically works as it did before.
— 23:44
The real power comes when you create a new conformance to the WeatherClientProtocol which allows you to employ different functionality from what the live client does. For example, we can create a mock weather client that specifies its responsible in a hardcoded, synchronous manner: struct MockWeatherClient: WeatherClientProtocol { func weather() -> AnyPublisher<WeatherResponse, Error> { Just( WeatherResponse(consolidatedWeather: []) ) .setFailureType(to: Error.self) .eraseToAnyPublisher() } }
— 24:40
For simplicity we are just immediately returning an empty array of weather results. And that may seem silly, but with one small change to our SwiftUI preview we can run the screen without ever making an API request: ContentView( viewModel: AppViewModel(weatherClient: MockWeatherClient()) )
— 24:55
Now this just shows a blank screen, but even that is kind of interesting! We could use this mock client to get understanding of how our application deals with empty states, for example maybe we should show a little message here letting the user know we don’t have any weather results for them. This already wasn’t possible when we were hitting the live API directly in the view model because I’m not aware of what location I can give the endpoint to guarantee that the server responds with an empty set of results.
— 25:28
But if we want to get something on the screen we can add a weather result to our mock: Just( WeatherResponse( consolidatedWeather: [ .init( applicableDate: Date(), id: 1, maxTemp: 30, minTemp: 10, theTemp: 20 ) ] ) )
— 25:50
And now we get a row displaying in the UI.
— 25:55
We could even exercise some of those edge cases we wondered about, such as negative temperatures: consolidatedWeather: [ .init( applicableDate: Date(), id: 1, maxTemp: 30, minTemp: 10, theTemp: 20 ), .init( applicableDate: Date().addingTimeInterval(86400), id: 2, maxTemp: -10, minTemp: -30, theTemp: -20 ) ]
— 26:26
And these previews render instantly and without needing an internet connection or a real API to hit over the network.
— 26:33
We could even substitute in a failure for our mock to see what happens when the API has an error: Fail(error: NSError(domain: "", code: 1)) .eraseToAnyPublisher()
— 26:45
We aren’t currently handling failures in the API, but we should probably show something in the screen or display an alert. If we had that functionality built we could easily see it in the preview without needing to emulate the failure by either turning off our internet or purposely hitting an incorrect API endpoint.
— 27:05
There’s another edge case that is hard to capture when hitting the live endpoint, but could be useful while testing. What if we wanted to see what happens when the API request takes a long time to return. We could simply add a delay to the publisher that returns our mock data: Just( WeatherResponse( consolidatedWeather: [ .init( applicableDate: Date(), id: 1, maxTemp: 30, minTemp: 10, theTemp: 20 ), .init( applicableDate: Date().addingTimeInterval(86400), id: 2, maxTemp: -10, minTemp: -30, theTemp: -20 ) ] ) ) .setFailureType(to: Error.self) .delay(for: 2, scheduler: DispatchQueue.main) .eraseToAnyPublisher()
— 27:26
And now when we run this the results don’t appear immediately, but will display after 2 seconds. We can clearly see that the user experience for a long running API request is not great. We should probably have a loading indicator or something.
— 27:42
And so now this is really cool. We are already seeing the power of identifying dependencies, making them explicit, and controlling them. We can exercise all of these edge cases by just feeding plain data to a mock instead of trying to change our environment to reproduce the cases we want to test.
— 27:57
And this style of dependency control is definitely the most popular style in the iOS community. There’s no shortage of articles and videos explaining how to put protocols in front of dependencies, and it’s even how Apple untangles dependency relationships in some of their own frameworks, such as protocols for delegates and data sources. Protocol-oriented problems
— 28:13
But, just because it’s popular doesn’t mean it can’t be improved. There is something a little strange about what we have come up with so far. We created a protocol to abstract away the interface of getting weather results, but only created two conformances: a live one that makes an API request and a mock one that synchronously returns mocked data.
— 28:33
A protocol that only has two conformances is not a strong form of abstraction. There is not a single protocol that Apple gives us that is meant to only have two conformances. For example, the Sequence and Collection protocols have dozens of conformances.
— 28:54
Now, we could of course create more conformances, like if we separated the empty, failed and happy path clients into separate types: struct EmptyWeatherClient: WeatherClientProtocol { func weather() -> AnyPublisher<WeatherResponse, Error> { Just(WeatherResponse(consolidatedWeather: [])) .setFailureType(to: Error.self) .eraseToAnyPublisher() } } struct HappyPathWeatherClient: WeatherClientProtocol { func weather() -> AnyPublisher<WeatherResponse, Error> { Just( WeatherResponse( consolidatedWeather: [ .init( applicableDate: Date(), id: 1, maxTemp: 30, minTemp: 10, theTemp: 20 ), .init( applicableDate: Date().addingTimeInterval(86400), id: 2, maxTemp: -10, minTemp: -30, theTemp: -20 ) ] ) ) .setFailureType(to: Error.self) .eraseToAnyPublisher() } } struct FailedWeatherClient: WeatherClientProtocol { func weather() -> AnyPublisher<WeatherResponse, Error> { Fail(error: NSError(domain: "", code: 1)) .eraseToAnyPublisher() } }
— 29:43
And then we can use any one of these conformances in our preview. ContentView( viewModel: ViewModel( // weatherClient: EmptyWeatherClient() // weatherClient: HappyPathWeatherClient() weatherClient: FailedWeatherClient() ) )
— 30:09
This is certainly possible, but it’s also a lot of code, and it’s resistant to changes to the core protocol. For example, soon we will be adding a new endpoint to our weather client: import CoreLocation struct Location {} protocol WeatherClientProtocol { … func searchLocations( coordinate: CLLocationCoordinate2D ) -> AnyPublisher<[Location], Error> }
— 31:06
And this has now broken 4+ different types, and it’s not clear how some of them should be fixed. For example, the “failed” weather client was meant to simulate when the weather request failed, but does that mean search for locations should also fail? Do we need yet another conformance to handle the combination of success and failures for each of these endpoints?
— 31:28
This could easily lead to explosion of new types that are conforming to this protocol as we add more endpoints to the protocol and as we find more combinations of behavior we want to model. One thing we could do is consolidate all of our mock types into a single, main mock, which allows the customization of each endpoint: struct MockWeatherClient: WeatherClientProtocol { var _weather: () -> AnyPublisher<WeatherResponse, Error> var _searchLocations: (CLLocationCoordinate2D) -> AnyPublisher<[Location], Error> func weather() -> AnyPublisher<WeatherResponse, Error> { self._weather() } func searchLocations( coordinate: CLLocationCoordinate2D ) -> AnyPublisher<[Location], Error> { self._searchLocations(coordinate) } }
— 32:24
We’ll give the field names underscores in order to differentiate them from the methods, otherwise the code may not compile.
— 32:32
Then, when constructing a mock client we can decide what kind of data we want to return, such as empty arrays for both endpoints: MockWeatherClient( _weather: { Just(WeatherResponse(consolidatedWeather: [])) .setFailureType(to: Error.self) .eraseToAnyPublisher() }, _searchLocations: { _ in Just([]) .setFailureType(to: Error.self) .eraseToAnyPublisher() } )
— 33:27
And so this one single conformance seems to have replaced the previous 3 conformances, and so that seems like a win. But again, we are back to the situation where we only have two conformances for this protocol: a live one and a mock one. And the mock implementation requires quite a bit of boilerplate to make it versatile.
— 33:51
Turns out we have essentially just recreated a technique that we have discussed a number of times on Point-Free, first in our episodes on dependency injection ( made simple , made comfortable ) and then later in our episodes on protocol witnesses . For the times that a protocol is not sufficiently abstracting away some functionality, which is most evident in those cases where we only have 1 or 2 conformances, it can be advantageous to scrap the protocols and just use a simple, concrete data type. That is basically what this MockWeatherClient type is now.
— 34:27
So, let’s just take this all the way. Let’s comment out the protocol: References Dependency Injection Made Easy Brandon Williams & Stephen Celis • May 21, 2018 Our first episode on dependencies, where we show that they can be wrangled quite quickly and effectively by introducing a global, mutable instance of a struct. Note Today we’re going to control the world! Well, dependencies to the outside world, at least. We’ll define the “dependency injection” problem and show a lightweight solution that can be implemented in your code base with little work and no third party library. https://www.pointfree.co/episodes/ep16-dependency-injection-made-easy Dependency Injection Made Comfortable Brandon Williams & Stephen Celis • Jun 4, 2018 Note Let’s have some fun with the “environment” form of dependency injection we previously explored. We’re going to extract out a few more dependencies, strengthen our mocks, and use our Overture library to make manipulating the environment friendlier. https://www.pointfree.co/episodes/ep18-dependency-injection-made-comfortable Composable Architecture: Dependency Management Brandon Williams & Stephen Celis • Feb 17, 2020 We made dependencies a first class concern of the Composable Architecture by baking the notion of dependencies directly into the definition of its atomic unit: the reducer. https://www.pointfree.co/collections/composable-architecture/dependency-management How to Control the World Stephen Celis • Sep 24, 2018 Stephen gave a talk on our Environment -based approach to dependency injection at NSSpain 2018. He starts with the basics and slowly builds up to controlling more and more complex dependencies. https://vimeo.com/291588126 Side Effects Brandon Williams & Stephen Celis • Feb 5, 2018 In our first episode on side effects we first show that side effects that depend on the outside world can be controlled by passing the dependency as input. Side effects can’t live with ’em; can’t write a program without ’em. Let’s explore a few kinds of side effects we encounter every day, why they make code difficult to reason about and test, and how we can control them without losing composition. https://www.pointfree.co/episodes/ep2-side-effects Protocol-Oriented Programming in Swift Apple • Jun 16, 2015 Apple’s eponymous WWDC talk on protocol-oriented programming: At the heart of Swift’s design are two incredibly powerful ideas protocol-oriented programming and first class value semantics. Each of these concepts benefit predictability, performance, and productivity, but together they can change the way we think about programming. Find out how you can apply these ideas to improve the code you write. https://developer.apple.com/videos/play/wwdc2015/408/ Protocol Oriented Programming is Not a Silver Bullet Chris Eidhof • Nov 24, 2016 An old article detailing many of the pitfalls of Swift protocols, and how often you can simplify your code by just using concrete datatypes and values. Chris walks the reader through an example of some networking API library code, and shows how abstracting the library with protocols does not give us any tangible benefits, but does increase the complexity of the code. http://chris.eidhof.nl/post/protocol-oriented-programming/ Downloads Sample code 0110-designing-dependencies-pt1 Point-Free A hub for advanced Swift programming. Brought to you by Brandon Williams and Stephen Celis . Content Become a member The Point-Free Way Beta previews Gifts Videos Collections Free clips Blog More About Us Community Slack Mastodon Twitter BlueSky GitHub Contact Us Privacy Policy © 2026 Point-Free, Inc. All rights are reserved for the videos and transcripts on this site. All other content is licensed under CC BY-NC-SA 4.0 , and the underlying source code to run this site is licensed under the MIT License .