Dynamically altering the environment at runtime to enable debug features e.g. switches for enabling mocked API-responses

In addition to @victor's idea there is a way to change the environment even though the environment is immutable and dependencies in the environment tend to the structs.

Since the dependencies in the environment tend to be simple structs with closure properties, those closures can close over some mutable state that is only visible inside the scope that creates instances of the dependency.

For example, say you had an ApiClient that had a few endpoints for request information from your API, but you also wanted an endpoint for changing the base URL of the API, that way while the app is running you could switch between staging servers and production servers. That client could have this kind of interface:

struct ApiClient {
  var setBaseUrl: (URL) -> Effect<Never, Never>
  var fetchEpisodes: () -> Effect<[Episode], ApiError>
  var favoriteEpisode: (Episode.Id) -> Effect<Void, ApiError>
}

And then the live implementation of that client could hold onto a bit of mutable baseUrl: URL state so that it could be changed whenever the setBaseUrl endpoint is called:

extension ApiClient {
  static func live(baseUrl: URL) -> Self {
    var baseUrl = baseUrl

    return Self( 
      setBaseUrl: { url in 
        .fireAndForget { baseUrl = url } 
      },
      fetchEpisodes: { 
        // make request using baseUrl
      },
      favoriteEpisode: { id in 
        // make request using baseUrl
      }
    )
  }
}

This is a style we have used quite a bit, and it keeps these seemingly messy customization endpoints in the effect layer, which means its still possible to test and verify that endpoints are being called the way you expect.

2 Likes