DEV Community

Pin-Sho Feng
Pin-Sho Feng

Posted on

Dependency injection in functional programming

Dependency injection in functional programming (Kotlin)

tl;dr

  • DI in FP can be achieved using partial function application or the Reader monad
  • But DI may not make sense in FP
  • An OO-FP-OO sandwich where the domain core is purely functional but the outer layers are OO and use a dependency injector could be a good approach for applications which have a lot of side-effects.
  • All solutions have pros and cons, final choice depends on the task at hand and personal preference

When I started looking into functional programming, I kept wondering how DI works in this paradigm and was surprised to find that there are not many articles written about it, especially for beginners, so I decided I could try to help in filling that void by sharing what I've learnt. Therefore, the target of this article are the developers that have started looking into the functional paradigm and wonder how dependency injection works there, so I'll try to introduce every topic without assuming much prior knowledge. The language I use is Kotlin, though the principles are universal. The article should preferrably be read from top to bottom, as the explanation of the concepts follow a logical order. Let's start!

Why functional programming?

Like many developers, the past couple of years I started gaining a lot of interest in functional programming due to all the problems I've seen in practice with mutable state. As an Android developer for the past 5 years, I've experimented these problems with the wide adoption of the MVP architecture. At the time it was all the rage as it allowed us to move away from untestable God-Activities/Fragments and have the presentation logic delegated to testable Presenters. As new features were implemented and requirements changed, our presenters would start to look like the old huge activities/fragments, and making any change would become increasingly dangerous, as presenters modify the view in imperative style (e.g. view.showLoading()) and multiple view-changing functions were in place. We'd split the presenters further but still, the complexity of not knowing if a change would affect something else was there. There had to be a better way!

One day I was still in the office (XING Barcelona) while a meetup was held in which the speaker was introducing Redux to the audience. I'm glad I decided to stay as it ultimately inspired me and got me to learn about functional programming. I started digging and discovered that Redux was partly inspired by Elm and then I started checking Elm.

Elm is an awesome pure functional language that tries very hard to be beginner friendly and while I think it does a great job, the paradigm shift is still enourmous. It's not so much the syntax itself, but the mindset change from an imperative to a functional one (to the Android folks, do you remember when you started with RxJava?). After some practice I began feeling comfortable with Elm, but I still remembered a conversation with a colleague in which he was trying to explain "monads" to me (Elm doesn't have them). No matter how many articles I'd read (including this one with pictures) I still wouldn't get it. The thing is that most, if not all articles focus on the "what", but not "why" we need monads. That's when I decided to go all-in and teach myself Haskell.

After reading LYAH and chunks of Haskell Programming (amazing book btw) I decided to do some practice by building an ArnoldC compiler for fun. The experience was very rewarding, even if I end up never using Haskell professionally. It's forever changed the way I look at code but while I do know what a monad is now, I'm also convinced that type classes (functors, applicatives, monads...) are not essential to functional programming, as Elm or Elixir show. Abstraction is a tool, not a goal! I'm aware this is opinionated and others may have a different opinion, which is fine. I think that type classes are especially useful for library writers when they strive to provide generic solutions but end-user application features rarely need such level of abstractions.

Joe Armstrong said "state is the root of all evil" and I subscribe to it. I've concluded that with these 2 principles you can get very far:

  • Immutability
  • Referential transparency (for a given input, you get the same output always with no side-effects)

As long as your programming language has first-class/top-level functions, just applying those 2 principles can get you a long way.

Pure functions excel at transforming data. For any given input, it'll return the same output, without side effects. This makes functional programming suitable for applications that resemble pipelines in which the input data is transformed through various stages. Examples of such applications are parsers, compilers, data analysis, etc.

But what about UI or CRUD applications? These sort of applications are basically full of side-effects. How can we fit a functional programming paradigm here? Doesn't this go against the 2 principles mentioned before?

So in the quest for trying to use functional programming to solve the same problems I've been running into the past few years, I've tried to force myself to strictly apply functional programming and see how it goes.

I built a sample server application in Kotlin to test the waters, having the 2 principles above in mind. It's a very simple Spring Boot application that exposes an endpoint that returns some data. Internally, the app will call another endpoint and process the results before returning a response. Fairly standard.

To be functional I followed these guidelines:

  • Use only functions, avoiding classes except for the glue initialization code. Having no classes and just functions encourages you to think in terms of data transformation and avoid state.
  • Use val and immutable data structures (Map, List...).
  • Avoid side effects.

I followed Clean Architecture and separated my code in 3 logical layers: presentation, domain and data. When the server receives a request, the logic will flow like

request handler -> interactor -> external services

All good. But then I ran into a problem... How do I achieve DI? My interactors (domain) are no longer classes so I can't inject them the repositories that they should be calling.

If you look up "functional programming dependency injection" in Google, you'll find that there's (perhaps) surprisingly very little literature written on the topic. I may have an idea why that's the case but bear with me and keep reading! I'll be covering two of the most typical approaches:

  • Partial function application
  • Reader monad

There are other styles that can help you achieve similar goals, such as the tagless-final pattern or free monads, but I'm not going to cover them in this article.

Dependency injection goals

Before we look at functional programming's solutions to DI, let's take a step back and reflect on the problem that we're trying to solve. Without going into too much detail, we could summarize the DI goals as:

  • Inversion of Control and loose coupling. Implementations don't choose their dependencies. This allows the caller to supply the specific implementations of these dependencies, allowing the behavior of the code to be configured externally. Typically, this is done by making classes depend on interfaces, the implementations of which are passed-in via constructor.
  • Unit testing. This derives from the previous goal, as it allows us to inject mock implementations into the class under test.

Let's take a look at how we might achieve these goals with functional programming.

Partial function application

Partial function application is a very cool concept in functional programming that consists in passing some of the parameters to a function and getting a function that accepts the rest of the parameters in return. This concept derives from currying, which is a technique by which any function of N arguments can be translated into a sequence of N functions of 1 argument. For example:

fun sum(a: Int, b: Int): Int = a + b
sum(1, 2) // returns 3

// can be converted into a function that accepts 1 argument (a)
// and returns another function that accepts 1 argument (b)
fun sum(a: Int): (Int) -> Int {  
    return { b -> a + b }  
}

val sum1 = sum(1)
sum1(2) // returns 3

How can this help us? Imagine that we want to be able to inject different loggers into a function. We might want one that simply prints its output to the console or we might want another one that stores the log in a file. We can partially apply the function with the logger and the resulting function won't need the logger parameter anymore.

typealias ItemFetcherFn = () -> List<Item>

fun fetchItemsLogged(log: (String) -> Unit): ItemFetcherFn  {
    return {
        log("Fetching items")
        // fetch and return items...
    }
}

// let's use partial application to "inject" some logger
val consoleLogger: (String) -> Unit = { str -> println(str) }
val fetchItems: ItemFetcherFn  = fetchItemsLogged(consoleLogger)

// we can now use fetchItems anywhere, without knowing it has a logger
fun onStart(fetchItems: ItemFetcherFn ) { ... }

onStart(fetchItems)

This technique was the one I used in the master branch of my sample project. As I mentioned before, I separated the code in 3 logical layers (clean architecture style): presentation (or controllers), domain and data. If you remember the dependency rule, the inner layer cannot access the outer layer. Our inner layer is the domain layer, so it shouldn't have references to the presentation and the data layer (note that a Repository is considered an interface to the data layer, and the domain can reference interfaces). With this in mind, let's analyze our example starting with the data layer's Services.kt:

fun getUserTodos(
    baseUrl: String,
    userId: Int
): Mono<List<Todo>> {
    return WebClient.create(baseUrl).get()
        .uri { uriBuilder ->
            uriBuilder.path("/users/$userId/todos")
                .build()
        }
        .retrieve()
        .bodyToMono(object : ParameterizedTypeReference<List<DataTodo>>() {})
        .map { dataTodos -> dataTodos.map { it.toDomain() } }
}

From the domain layer's perspective, this function has a baseUrl, which is clearly a data layer detail. From the domain we shouldn't be concerned about where we get the data from. If we were using OO, this function would be encapsulated in a Repository object and baseUrl could be injected via constructor, so the function signature that the domain layer would end up using is fun getUserTodos(userId: Int). How can we achieve this in functional programming style? Using partial function application! We could partially apply baseUrl. Let's see how this looks in practice for Interactors.kt:

typealias TodoFetcherFn = (userId: Int) -> Mono<List<Todo>>

fun getCompletedTodos(fetchTodos: TodoFetcherFn, userId: Int): Mono<List<Todo>> {
    return fetchTodos(userId).map { todos -> todos.filter { it.isCompleted } }
}

Our domain function depends on a function of type TodoFetcherFn that takes a userId and returns a Mono<List<Todo>>. We're not depending on any specific implementation and as such the function is unit testable, so we achieved our DI goals. As long as we pass in a function that complies with this signature, we're good to go!

Let's see how this function is partially applied and "injected". This is Todos.kt from the controller layer:

fun getTodos(request: ServerRequest, fetchTodos: TodoFetcherFn): Mono<ServerResponse> {
    val userId = request.pathVariable("id").toIntOrNull()

    return if (userId != null) {
        val todosResponseMono =
            getCompletedTodos(fetchTodos, userId)
                .map { it.toResponse(userId) }
                .onErrorResume { e -> internalServerError(e) }

        ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
            .body(todosResponseMono, GetTodosResponse::class.java)
    } else {
        ServerResponse.badRequest().syncBody("id is not an integer")
    }
}

And finally this is the injection root (SampleApplication.kt):

@Configuration
class Router @Autowired constructor(
    @Qualifier(TODOS_BASE_URL) private val todosBaseUrl: String
) {

    @Bean
    fun routes(): RouterFunction<ServerResponse> = router {
        accept(MediaType.APPLICATION_JSON).nest {
            GET("/user/{id}/completedtodos") {
                getTodos(it, getTodosFetcherFn())
            }
        }
    }

    private fun getTodosFetcherFn(): TodoFetcherFn {
        return ::getUserTodos.partially1(todosBaseUrl)
    }
}

The sample is using Arrow to partially apply the first argument of getUserTodos (baseUrl). This function is then passed to the route handler, which then calls the interactor function with it.

Using partial function application we've successfully achieved FP's version of dependency injection. However, you might have some questions.

How do you test the route handler (getTodos) in isolation?

Fair question. If you want to test getTodos, you'd inevitably test getCompletedTodos as well as it makes a direct call to it. My advice is to not test it in isolation, test the whole feature. Just use a mock web server and test the whole feature end-to-end. There are many benefits to this approach.

Still, this is my opinion and you may not agree. If you really want to test it separately, you could make getTodos depend on a function that you'd pass in instead of having it call getCompletedTodos directly. I accidentally did that in a previous commit, so you could check how it'd work. Note that that implementation is wrong, the fetchTodos function that's passed in should have a signature separate from TodoFetcherFn from the domain layer, even if they look the same, so that we keep the functions independent.

If you have 5 dependencies, do you pass 5 arguments down?

My example is unrealistic as any real application will likely have more dependencies than that. For simplicity I'm just passing the function that I know I'll use, but you could construct a composition root in which you partially apply the functions that will have to be called as we go through the layers. Then you only have to pass the functions that will have to be called in the immediately next layer. Check how this works here. Another approach would be to pass a single argument with all the dependencies, which is what you'd do with the Reader monad.

The dependencies appear as arguments in the function signature of all functions in the call chain

Indeed. Whether that's cool or not is subjective, but there's a technique in functional programming that allows us to pass an argument implicitly so that it disappears from the argument list of your functions. Meet the Reader monad in the next section!

Code navigation is more difficult as cmd + click doesn't work on partial function signatures

Yes. If you think about it, the key idea of using partial application for DI is loose coupling, so you're not supposed to know which function was originally partially applied. If we organize the code in a way that we don't have deeply nested calls of partially applied functions, it should be simple to find the root.

I hope I managed to explain the technique in an understandable way, but if it's not enough or if you simply would like to see another take, there's an awesome article by Scott Wlaschin explaining it with a more complex example.

The Reader monad

We've found that passing the same argument down a chain of functions can be (subjectively) annoying. I mentioned that the Reader monad can allow us to pass that argument implicitly... how's that possible? We could put it in some global value and access it directly where needed. We no longer need to pass it to any function! But then this is just hardcoding and beats completely the purpose of dependency injection, in case we lost sight of our initial goals.

Alright, so what is the Reader monad about? To properly understand it we'd first need to understand what's a monad and why do we need it. Before that, you'd need to be introduced to functors and applicatives. It personally took me a lot of time to figure it out, even after reading many explanations and seeing them represented as pictures. I actually never got it until I actually used them in practice, so I'm not going to attempt to explain all the concepts here.

Instead, let's see if we can build an intuition about it. We can start by stating our goals clearly:

  • Pass an argument to all functions in a call chain
  • Do so without explicitly having it in the parameters list of these functions

If we call a function that returns a value, we get that value straight away. However, if we want to defer this computation, we can wrap that value in a function and return it. Then we won't get the value until we call this returned function.

fun giveMe5(): Int = 5

fun deferGiveMe5(): () -> Int = { 5 }

giveMe5() // returns 5
deferGiveMe5()() // returns 5

What if we connected a bunch of functions that return functions that take the same input? The result would be a function chain that wouldn't be evaluated until we actually call it and then we can pass some input down the chain.

fun delegateNum(): (Int) -> Unit {
    return { num -> furtherDelegateNum()(num) }
}

fun furtherDelegateNum(): (Int) -> Unit {
    return { num -> printNum()(num) }
}

fun printNum(): (Int) -> Unit {
    return { num -> println(num) }
}

val chain = delegateNum()
chain(5) // prints 5

5 in this case would be the "thing" that we want to inject down the chain. It could be the dependency that we need to make available to all the functions in it and it's implicit! So we achieved the goals we set before.

Now, notice how delegateNum(), furtherDelegateNum() and printNum() have the same structure. It turns out we can abstract this and this is loosely speaking what the Reader monad is about! Create a lazy (as in deferred evaluation) chain of functions that can read a common value, and then run this chain passing the value that we want.

Hopefully at this point I managed to give you an idea of the Reader monad, so let's see it with a real example. I didn't create a version of our example but we can use one from Jorge Castillo that shares a similar architecture to the one I used previously, although his example is an Android app. In his example he's fetching a list of superheroes from some endpoint. Let's check the data source:

// MarvelNetworkDataSource.kt

/*
 * This is the network data source. Calls are made using Karumi's MarvelApiClient.
 * @see "https://github.com/Karumi/MarvelApiClientAndroid"
 *
 * Both requests return a new Reader enclosing an action to resolve when you provide them with the
 * required execution context.
 */
fun fetchAllHeroes(): Reader<GetHeroesContext, IO<List<CharacterDto>>> =
    ReaderApi.ask<GetHeroesContext>().map({ ctx ->
      IO.monadDefer().binding {
        val result = runInAsyncContext(
            f = { queryForHeroes(ctx) },
            onError = { IO.raiseError<List<CharacterDto>>(it) },
            onSuccess = { IO.just(it) },
            AC = ctx.threading
        ).bind()
        result.bind()
      }.fix()
    })

The value that's passed down through his function chain is ctx, which is of type GetHeroesContext, which is a data class with all the dependencies that this chain needs (see SuperHeroesContext.kt).

This function is called from getHeroes(HeroesRepository), which is called from HeroesUseCases.kt:

fun getHeroesUseCase() = getHeroes(NetworkOnly).map { io -> io.map { discardNonValidHeroes(it) } }

which is then called from getSuperHeroes(SuperHeroesPresentation.kt), which is finally called from SuperHeroListActivity.kt:

override fun onResume() {
    super.onResume()
    getSuperHeroes().run(heroesContext)
}

Notice how heroesContext is passed down the function chain through the run function and how there's no trace of it in the parameters of the functions in the chain.

So this is how you do DI with the Reader monad! If you feel the need for a more technical explanation, check this post from the author himself.

Now, you might again have some questions...

How do you decide between the Reader monad and partial application?

This is a common question. The main reason people usually use the Reader monad is to avoid passing the same argument over and over again. Still, this may be subjective.

I said in the introduction that in my opinion these sort of abstractions (typeclasses) are not strictly necessary and we have an alternative to it, but whether you prefer it or not is subjective to a certain point. I personally don't currently feel I need them with the approach that I'll suggest later (I will tell you about it below, keep reading!).

Aren't we coupling all the functions in the Reader chain to the value that we're passing?

I think so... any function in the chain will be able to access the value that we're passing. In the example above, any of these functions has access to GetHeroesContext. I'll go in more detail in the next section.

A critical view

We've seen 2 ways of achieving DI in functional programming, but while we did achieve the goals that we set in the beginning, is this way of doing things better than having a dependency injection framework in OO style?

Regarding partial function application, a user named Onur left a thought-provoking comment in the Scott Wlaschin's article that I already mentioned before:

This is a much needed article unfortunately it misses the point. Normally, objects are not composable. Dependency Injection is a cure for that. Just like functional programming uses functions to build larger programs, dependency injection is used to build larger object programs. In your examples, dependencies are only represented in 1 level but there can be far more deep hierarchies. Secondly, dependency injection registration is a way to achieve inversion of control, such that the components don't create themselves, but registrations are like a recipe that describes the dependencies and hierarchy and is external to the application. Thirdly, dependency injection also incorporates to the life times of the components. Finally, all of these imply impurity.

This is indeed very true. The composition root in our case is arguably not external to our application. We've got it in our router function. We could tuck it away by putting it into some other file, but we still need to manually define the partial function applications that we want to pass to our specific entry function, whereas with a DI framework we'd simply define the provider functions somewhere and then simply declare what we want to use in the constructors.

Lifetime is another thing... lifetime as such doesn't make sense in a pure context but things such as a database connection have a lifetime. So DI implies impurity, does it make sense to try to do it in the functional world?

Let's come back to the last concern of the previous section. Isn't using the Reader monad in our example implying that we're coupling all our functions in the chain to the dependency context that we're passing? Let's check SuperHeroesContext:

sealed class SuperHeroesContext(ctx: Context) {

  val heroDetailsPage = HeroDetailsPage()
  val apiClient
    get() = CharacterApiClient(MarvelApiConfig.Builder(
        BuildConfig.MARVEL_PUBLIC_KEY,
        BuildConfig.MARVEL_PRIVATE_KEY).debug().build())
  val threading = IO.async()

  data class GetHeroesContext(val ctx: Context, val view: SuperHeroesListView) : SuperHeroesContext(ctx)
  data class GetHeroDetailsContext(val ctx: Context, val view: SuperHeroDetailView) : SuperHeroesContext(ctx)
}

This context has 5 dependencies. Let's see MarvelNetworkDataSource.kt again:

fun fetchAllHeroes(): Reader<GetHeroesContext, IO<List<CharacterDto>>> =
    ReaderApi.ask<GetHeroesContext>().map({ ctx ->
      IO.monadDefer().binding {
        val result = runInAsyncContext(
            f = { queryForHeroes(ctx) },
            onError = { IO.raiseError<List<CharacterDto>>(it) },
            onSuccess = { IO.just(it) },
            AC = ctx.threading
        ).bind()
        result.bind()
      }.fix()
    })

We can see that we're accessing ctx.threading and ctx is passed to queryForHeroes, which internally accesses apiClient. But what's impeding us to also access ctx.view or ctx.heroesDetailPage? Should those be available to the data layer? I think we're violating the dependency rule. Besides that, generally speaking it's a good idea to strive for "making impossible states impossible" (see talks by Richard Feldman and Patrick Stapfer, or this article). If we can't access ctx.view, there's no way we can do anything about it!

We've showed that we can achieve DI with partial function application or the Reader monad, but the question is, should we?

OO-FP-OO, a hybrid approach

Let's remind ourselves of the things we like most about functional programming:

  • Immutability. Once a data value is created, we can never modify it.
  • Referential transparency. Given a function and an input value, you receive the same output, without side effects, every single time.

These properties make it easier to reason about programs, as you can be sure about the behavior being consistent. If the language is statically typed, equational reasoning becomes possible. This is going to make us have less bugs and less cognitive load, as your thought context should just be the function you have in front.

Typical Android applications or backend CRUD services are full of side effects. You need to access the network or a database to retrieve some data, and then you need to modify the UI to display this data or return some Json response. On top of that, like most languages Kotlin does not have a way to enforce referential transparency, so you can use any typeclass that Arrow provides but there's nothing preventing you from doing side effectful code that can be something as apparently harmless as a println or you could just call Thread.sleep(...) and stop the world. In practice, this means we need to follow some guidelines and make sure everybody in our team is aware of them.

If we need to access the network or the database and we can't enforce purity anyway, why aren't we even more pragmatic and simply do these in OO style? Our domain logic is what can be isolated from external factors such as UI or the data layer, so we could write it in FP style. If you view our business logic as a deterministic black box, you can just pass some data in (loaded from a DB), get some data out and render it to the UI.

data layer input -> domain logic -> output UI

As you can see, the domain logic is in the middle. If the rest is OO, we get a OO-FP-OO sandwich (I was inspired by Dax Fohl for the concept, although he's suggesting an FP-OO-FP approach).

Going back to what this article is about and the open question left at the end of the previous section, we could continue to leverage our DI framework and just keep our business logic core functional. Note that this doesn't mean that we should start having mutable state everywhere in the OO part. My approach would be "prefer FP over OO", which means to generally have an FP mindset but be pragmatic and use OO sparsely when it's much more convenient and it's not dangerous.

I tried this approach in another branch of the example. The domain logic stays exactly the same as in the partial application case:

// Interactors.kt
fun getCompletedTodos(fetchTodos: TodoFetcherFn, userId: Int): Mono<List<Todo>> {
    return fetchTodos(userId).map { todos -> todos.filter { it.isCompleted } }
}

However, now Repositories.kt takes advantage of Spring Boot's dependency injection system to inject the base url:

@Component
class UserTodosRepository @Autowired constructor(
    @Qualifier(ExternalServices.TODOS_BASE_URL) private val baseUrl: String
) {
    fun getUserTodos(userId: Int): Mono<List<Todo>> {
        return WebClient.create(baseUrl).get()
            .uri { uriBuilder ->
                uriBuilder.path("/users/$userId/todos")
                    .build()
            }
            .retrieve()
            .bodyToMono(object : ParameterizedTypeReference<List<DataTodo>>() {})
            .map { dataTodos -> dataTodos.map { it.toDomain() } }
    }
}

And it's injected into the route handler, who only passes the UserTodosRepository::getUserTodos function to the domain function.

@Component
class TodosHandler @Autowired constructor(
    private val repository: UserTodosRepository
) {
    fun getTodos(request: ServerRequest): Mono<ServerResponse> {
        val userId = request.pathVariable("id").toIntOrNull()

        return if (userId != null) {
            val todosResponseMono =
                getCompletedTodos(repository::getUserTodos, userId)
                    .map { it.toResponse(userId) }
                    .onErrorResume { e -> internalServerError(e) }

            ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
                .body(todosResponseMono, GetTodosResponse::class.java)
        } else {
            ServerResponse.badRequest().syncBody("id is not an integer")
        }
    }
}

As we're using the DI framework, we no longer need to have the partial application boilerplate in the outer layers. Also, our data layer could just request the dependencies it requires, instead of having a context injected with the dependencies required across all the layers involved in a feature, like in the Reader monad case.

A purist could say that the functional core shouldn't know anything about a getUserTodos function, but should be fed the user todos directly. Our domain function would simply filter by isCompleted and do nothing else. Essentially, side-effects are pushed to the outer layers. This style is similar to the "Ports and Adapters" architecture and is well described by Mark Seemann here (warning: F# and Haskell ahead!). Fair enough, though you could also argue that we'd do that at the expense of having the business logic pushed to the outer layers just so we can have a pure functional core.

In any case, you can see that we can successfully mix OO and FP and use them for what they're best at. DI frameworks are convenient and as long as we keep the side-effects well encapsulated in our OO parts and the pure data processing parts in FP style, we can have the best of both worlds!

Conclusions

We've seen that using partial function application or the Reader monad we can have similar results to OO-style dependency injection. However, they may not be the best approaches as they seem more like an almost direct translation of the OO pattern into the functional world.

OO and FP are not mutually exclusive, we can use both of them where they work best. An OO-FP-OO sandwich in which the business core is functional and outer layers use OO may be a pragmatic approach forward.

In any case, each approach has its pros and cons and which one you choose will depend on your task at hand and your own preferences.

Partial function application

+ Functional approach (no classes)
+ Explicit, no magic
+ Simple to implement
- Verbose (in Kotlin + Arrow)
- Lots of manual setup in the composition root.

Reader monad

+ Functional approach (no classes)
+ Relatively explicit
- Single parameter passed down through all layers (if used as in the example we used)
- Manual setup in the composition root
- Different layer dependencies may be accessible to all functions in the chain

OO-FP-OO, hybrid approach

+ Concise code
+ Easy to specify dependencies
+ DI framework takes care of the composition boilerplate
- DI is magical, especially when you're not familiar with the framework
- Uses classes
- Classes may encourage stateful code
- Developers need to be strict in trying to keep the functions pure and making sure that classes are only used for DI and not to hold state

I personally find the hybrid approach a good pragmatic one in Kotlin, but be mindful about the cons, especially the last one.

This was a long post! I hope you enjoyed the read and see you next time!

Top comments (22)

Collapse
 
patroza profile image
Patrick Roza • Edited

I like the Partial application approach, I use it in Typescript with a custom DI container which resolves also functions, not just class instances.

/**
 * Configures a function for dependency injection, dependency types are automatically inferred based on
 * the passed `deps` configuration. Dependency configuration is provided as last argument.
 */
export function configure<
  TDependencies extends DependencyDefinitions,
  TFunctionConstructor extends (dependencies: A.Compute<Dependencies<TDependencies>>) => TFunction,
  TFunction
>(func: TFunctionConstructor, deps: () => TDependencies) {
  const injectedFunc = (func as any) as ConfiguredFunction<TDependencies, TFunctionConstructor, TFunction>
  injectedFunc.$$inject = deps
  return injectedFunc
}
Enter fullscreen mode Exit fullscreen mode

called like so:

const myFunction = configure(
  function ({ dep1, dep2 }) { // the types of dep1 and dep2 are inferred.
    return (input: string) => ... do stuff
  },
 () => ({ dep1, dep2 }), // a function, so that it supports late-binding
)
Enter fullscreen mode Exit fullscreen mode

Of course you could store the dependency configuration also in some map, instead of writing onto the function, it works fine like this in Typescript, not sure you could do this in other languages.

as to

though you could also argue that we'd do that at the expense of having the business logic pushed to the outer layers just so we can have a pure functional core

I'm not sure how the list of todos is considered business logic. So retrieving data is imo something we can do in the app layer. Impure effects however, e.g on 3rd party apis, message busses, etc, I would handle with Domain events - do all the business decisions, logic, policy in the domain, put the decisions in an Event and let the event handlers call the relevant infra.

Collapse
 
psfeng profile image
Pin-Sho Feng • Edited

Thanks for sharing! I'm not familiar with the JS/TS world but out of curiosity and ignorance, why not Lodash or Ramda? Don't they support partial function application? What is the purpose of late-binding (assuming the dependencies are immutable references)?

I'm not sure how the list of todos is considered business logic. So retrieving data is imo something we can do in the app layer. Impure effects however, e.g on 3rd party apis, message busses, etc, I would handle with Domain events - do all the business decisions, logic, policy in the domain, put the decisions in an Event and let the event handlers call the relevant infra.

Agreed, that sounds like an approach where the code would be pure. I was referring to the difference between

getCompletedTodos(repository::getUserTodos, userId)

vs

val todos = repository.getUserTodos(userId)
getCompletedTodos(todos, userId)

In the first version, getCompletedTodos is referentially transparent only if repository::getUserTodos is too, which can't be guaranteed.

Collapse
 
patroza profile image
Patrick Roza • Edited

Follow-up, I think my confusion comes from our different definition of the Domain.
See for example this naive implementation in Typescript, for how I see it:
gist.github.com/patroza/8f2d634b23...

Main take aways:

  • The Interactor is not Domain, but Application, and is allowed to leverage interfaces from Infra.
  • The return view from the Interactor is complete, including userId. The only job of the Controller (request handler) is to transform HTTP request details into basic data structure for input to the interactor, and to transform the Response from the basic data structure into JSON.
  • I added a completeTodo mutation, because I think commands are a lot more interesting, because they require business rules/policy to be applied usually - which is where the domain model shines. where-as queries are often just bolted on top of database/remote apis, just a simple View, often pre-generated in a model convenient for Reading (if you follow CQRS for sure).

Finally, I think that for the Interactor taking the input as parameters and resolving the result by returning it as value, is a shortcut to the Clean Architecture's "Controller" calls Input interface implemented by "Interactor" and "Interactor" calls Output interface implemented by "Presenter", but I found that I hardly need the extra level of complexity it brings if you go literal.

Thread Thread
 
psfeng profile image
Pin-Sho Feng

Indeed, we had different definitions of Domain. I was thinking in terms of three layers: Presentation, Domain and Data, where domain includes business rules that are encoded in the use cases or interactors, like depicted here from this post.

If I'm understanding well what you say, your layers are the ones suggested in Clean Architecture by Uncle bob where you have, from inner to outer circles:

  • Domain models and functions that can transform them
  • Application layer that decides what domain models and what transformations to use and handle interactions with infra/presentation
  • Infra/Presentation - this would be where the data comes in or goes out.

Makes a lot of sense too, it reminds me of the "ports and adapters" architecture, where your infra/presentation functions would be the ports and adapters. My understanding of Domain is basically Application + Domain in your case I'd say.

I think we're talking about the same thing. In your code

const getCompletedTodos = (getTodos: ReturnType<typeof getTodosApi>)

getCompletedTodos is pure iff getTodos is pure. To make it 100% referentially transparent you'd have to pass the Todos, but then the getTodos logic would be pushed to the infrastructure layer.

Small nit: you could make your domain pure by passing in new Date() instead of instantiating it within complete.

Thread Thread
 
patroza profile image
Patrick Roza • Edited

Thanks!

I would say we got as far pure as makes sense. The HTTP Request Handler can't be pure, and the application use-case interactor shouldn't be pure, imo - at least if you like the Interactor to still add meaningful value and sticking to the fact that the Request Handler is just a delivery mechanism detail. However the pure business case, is pure.

I would say that the Interactor is the red parts here, blog.ploeh.dk/2020/03/02/impureim-... while the HTTP request handler is just the mount point for this usecase or workflow.

They do something similar in fsharpforfunandprofit.com/ddd/ which im a huge fan of, I read his book and am intruiged by his railway oriented programming.

Small nit: you could make your domain pure by passing in new Date() instead of instantiating it within complete.

Very good point! I'm still too stuck in non FP languages, that I don't realise these subtle cheats :)
gist.github.com/patroza/8f2d634b23...

How about the Exception? Is that considered Pure? The alternative would be the use of Either.

Thread Thread
 
patroza profile image
Patrick Roza • Edited

Update:
Regarding the post; fernandocejas.com/2014/09/03/archi...
I find the demo project a little boring due to it only implementing Queries, and the Interactors doing nothing more than redirecting calls to a Repository. At least some mapping of Entities to Views would be interesting, but Commands would be of more interest to me :)

I'm personally also more fan of Database contexts, which give lower level access to the data, instead of the more high level one exact query per method like in Repositories, as often in my experience each method is actually only used once, or maximum twice anyway.
But of course you don't give your entities access to these database contexts, that remains in the Interactor. Anyway, it depends on the project.

Old but gold: lostechies.com/jimmybogard/2015/05...
Basically I see Interactors as either Queries or Commands as per how JimmyBogard described them.
What I like about this pattern also is that you can extend the Mediator pipeline with additional behaviours through decorators like validating requests, performing authentication, etc.

Finally - if you're interested, here are some of my own experimentations with all these patterns, in a Functional Programming style, latest fp-ts version github.com/patroza/fp-app-framework2

It's doing CQRS with separate read and write contexts - file-based json databases - living the Uncle Bob dream ;-), domain events, DI, exploring functional programming and trying to find a balance between FP and OO, e.g FP-OO-FP sandwich, which may or may not lead to a 100% FP implementation ;-) Multi level validation (basic JSON fields in delivery mechanism, and Domain level validation in the Interactors/Domain objects)
It's runnable, and has a documented schema so you can easily discover what to post.

Thread Thread
 
psfeng profile image
Pin-Sho Feng

How about the Exception? Is that considered Pure? The alternative would be the use of Either.

I'd say that if the exception is recoverable it's not really an exception but a valid case so it should be modelled as such.

For example, you could have something like

type Todo = IncompleteTodo | CompletedTodo

and then you get compile-time verification plus no exceptions. I just happen to have written about it very recently (shameless plug :D).

I find the demo project a little boring due to it only implementing Queries, and the Interactors doing nothing more than redirecting calls to a Repository. At least some mapping of Entities to Views would be interesting, but Commands would be of more interest to me :)

Yeah, the example is perhaps a bit too simple, but the repository pattern is useful because it abstracts from the data source that's going to be used. From an interactor's point of view, it only wants to fetch data from a repository, but it doesn't care if this data comes from a disk file, database or Redis. These data sources can even be mixed, you might want to check if you've got something in Redis and if not, go fetch it.

Old but gold: lostechies.com/jimmybogard/2015/05...
Basically I see Interactors as either Queries or Commands as per how JimmyBogard described them.

That's a nice one. If we simplify things to the extreme, you can strip classes and interfaces and have only functions left. We can think of interactors like modules of query/command functions and if these are pure, it should be very easy to compose them with extra functionality such as validation or authentication.

I'll check your repo, thanks :)

Thread Thread
 
patroza profile image
Patrick Roza

I'd say that if the exception is recoverable it's not really an exception but a valid case so it should be modelled as such.

Fair enough. Will definitely check your post.

but the repository pattern is useful because it abstracts from the data source that's going to be used

I agree, I guess the main difference is just - do you put all functions in a Repository class, or do you have a bunch of separate functions - perhaps living closer to the usecases that they are used in, perhaps even as private functions to the usecase (function or file), i'm leaning more to the latter, as growing a repository class with single use functions seems to me just not that useful. It centralises them, but as they're often single-use, their ideal place seems to be more close to their use.

That's a nice one. If we simplify things to the extreme, you can strip classes and interfaces and have only functions left.

Yep, 100 with you, I prefer doing this with functions. When the language allows, doing this with just functions is awesome.
Except on the pure part; i'm fine with the Interactors depending on impure functions (only known by interface, not implementation), imo that's their main purpose;
receiving input from delivery mechanism - calling impure, pure, impure - producing output for the delivery mechanism.

Thread Thread
 
patroza profile image
Patrick Roza

Small follow-up on Repository, so i'm kind of more in the boat of:

find();
findOne(1); // find by id
findOne({ firstName: "Timber", lastName: "Saw" }); // find by query

and then using these functions in the interactor, instead of encapsulating specific queries in single use methods in the Repository.

Thread Thread
 
patroza profile image
Patrick Roza • Edited

Of course Jimmy has a word on that too ;-)
lostechies.com/jimmybogard/2012/10...
(it stands at the basis for the Query/Command handlers, which is an evolution of this)

Collapse
 
patroza profile image
Patrick Roza • Edited

Thanks for the response!

why not Lodash or Ramda

I'm a fan especially of Rambda (notice bd), I use it a lot and like it. I throw in some Lodash once in a while, when I need some of their helpers for a specific problem.

The problem with the various ways of composition in Typescript is some loss of (generic) type inference. I think Lodash barely tries. Rambda does way better, I would have to look into Ramda to check how they fare. If TS will receive placeholder generic types, this might improve.

Such type inference is very valuable to me, it is one of the things I like the most about Typescript; types are optional - although I use them extensively, and the inference goes a very long way, together with derivative types, to potentially reduce a lot of manual typing (Pick, Omit, Partial, NonNullable, ReturnType, to name few built-in. But one can go extreme; github.com/pirix-gh/ts-toolbelt)

So far i'm using github.com/gcanti/fp-ts (and all it's friends like io-ts etc). But i'm still not sure of the verbose syntax. I can't wait until the pipe operator (|>) in JS/TS lands, in the hopes that will resolve some of the boilerplate and verbosity. But maybe i'm really just ready for Kotlin, F# and friends..

What is the purpose of late-binding (assuming the dependencies are immutable references)

I like to be able to define less abstract functions below my most abstract one (in class world, basically from Public to Private methods). Without late-binding I would have to define those functions above instead.
Also it helps a little bit with some annoying cyclic references between files at times - although those should generally be avoided anyway.

I was referring to the difference between

I agree, but that means I still don't understand the sentence.

I read it as:
"If you do this:

val todos = repository.getUserTodos(userId)
getCompletedTodos(todos, userId)
Enter fullscreen mode Exit fullscreen mode

You essentially push business logic to the outer layers, for the sake of having a functional core."

But I don't see how doing this is moving business logic.
Perhaps i'm misreading ;-)

Thread Thread
 
psfeng profile image
Pin-Sho Feng

Thanks a lot for this very high quality response, it all makes sense. I'm very intrigued by TypeScript right now to be honest, will give it a try :)

Collapse
 
koresar profile image
Vasyl Boroviak • Edited

Few people, few articles, over few years - I couldn't understand how to do DI in FP. I finally get it! Your explanation is great.

I read some opinions in the internet that mocking (which is another term for DI) is a code smell. Would you agree with that?

Collapse
 
psfeng profile image
Pin-Sho Feng • Edited

Thanks a lot, I'm very glad to hear that!

I generally agree that mocking is a smell. Note that mocking refers to a testing technique, it's not a synonym for DI. Strictly speaking, mocking is about verifying that certain methods are called on some object and as such you're testing implementation details. See here.

Testing implementation details has the problem that it makes tests more difficult to maintain. For example, if you change the name of the method you're expecting to be called, you have to change all the tests that depend on this method, but the behavior may be exactly the same.

I prefer feature testing and avoid mocking as much as possible. Check these resources:
blog.twitter.com/engineering/en_us...
blog.kentcdodds.com/write-tests-no...

Collapse
 
koresar profile image
Vasyl Boroviak

I mock only I/O related things in my tests. Thus I call it mocking. Probably wrong term usage. Apologies.

Do I understand it right that a way of replacing side effect function is called DI in the article above?
And isn't it just a way to mock that function?

I'm confused.

Thread Thread
 
psfeng profile image
Pin-Sho Feng

DI refers to dependency injection, which is basically passing a dependency as a parameter to a function instead of having the function call it directly. For example:

fun doSomething() {
    fetchItemsFrom("http://someurl.com/items") // this is hardcoding the URL that you want to use
}

fun doSomething(url: String) {
    fetchItemsFrom(url) // now url is "injected" to the function.
} 

fun doSomething(fetchItems: () -> Unit) {
    fetchItems() // you can also choose to pass the whole function
}

DI and mocking for testing are related in that you can pass mocks (or stubs) to the function under test. If what you're injecting is a side-effectful function, you can indeed replace it with some mock function that doesn't have side-effects, just for the purposes of testing.

Let me know if it's clearer now!

Thread Thread
 
koresar profile image
Vasyl Boroviak

The only difference I see is that with DI the replaced value can be data.

Sorry. I still believe DI and ability to mock is the same concept - ability to replace a thing down the call chain.

Thread Thread
 
psfeng profile image
Pin-Sho Feng

Dependency injection is a concept and the ability to mock is a consequence of this concept, but it's by no means the only benefit.

You could, for example, use some class across different sections of an app but have it behave differently depending on the section. Using the dependency injector you can configure the different instances of the class that will be used in each section.

Collapse
 
erdo profile image
Eric Donovan

Really excellent article! helped a lot with my understanding, thanks for taking the trouble :)

Collapse
 
carstenk_dev profile image
Carsten

some of the problems you noticed with Reader vanish if you use functions like withReaderT

overall much of this is a problem of composing getters/setters - one of the answers to that is lenses/optics and so maybe you'll find this lens-module interesting too (deals with Readers)

Of course in languages like Elm or F# this might not be an option but Scala, PureScript and - I think - Kotlin with Arrow you might get lucky ;)

Collapse
 
psfeng profile image
Pin-Sho Feng

Hey Carsten, thanks for the feedback. Could you please provide an example of how withReaderT would solve those problems?

Collapse
 
zenventzi profile image
Zen Ventzi

WOW! Hands down the most insightful engineering articles I've read about fp/oop relationship. Thank you very much for putting this piece of art together.

That's the kind of devs I'd love to be working with!