1

P.S. example is in kind-of-scala, but language not really matter, I am interesting in functional approach in a whole.

Usually I saw pattern like this

outer world -> controller -> serviceA -> serviceB -> DB Accessor

So we got layers where function of outer layer calls function of inner layer. (I drop IO monad wrapping for simplicity)

class Controller(service: ServiceA) {
  def call(req): res {
    val req1 = someWorkBefore(req)
    val res1 = service.call(req1)
    someWorkAfter(res1)
  }

  private someWorkBefore(req): req1
  private someWorkAfter(res1): res
}

class ServiceA(service: ServiceB) {
  def call(req1): res1 {
    val req2 = someWorkBefore(req1)
    val res2 = service.call(req2)
    someWorkAfter(res2)
  }

  private someWorkBefore(req1): req2
  private someWorkAfter(res2): res1
}

class ServiceB(db: DBAccessor) {
  def call(req2): res2 {
    val req3 = someWorkBefore(req2)
    val res3 = service.call(req3)
    someWorkAfter(res3)
  }

  private someWorkBefore(req2): req3
  private someWorkAfter(res3): res2
}

The problem I see here is all functions are "not pure" and to write a test of some component one should make a mock of it's inner component, whitch is not good in my opinion.

Other option is to forget about separation of concerns in some way, and put every thing in one place. (I drop IO monad wrapping for simplicity)

class Controller(serviceA: ServiceA, serviceB: ServiceB, db: DBAccessor) {
  def call(req): res = {
    val req1 = someWorkBefore(req)
    val req2 = serviceA.someWorkBefore(req1)
    val req3 = serviceB.someWorkBefore(req2)
    val res3 = db.call(req3)
    val res2 = serviceB.someWorkAfter(res3)
    val res1 = serviceA.someWorkAfter(res2)
    someWorkAfter(res1)
  }

  private someWorkBefore(req): req1
  private someWorkAfter(res1): res
}

Which looks better because every function in services is pure in some way and do not depends on other stuff that should be mocked, but function of Controller is now a mess. What other options of architecture could be considered?

6
  • The problem here is not FP and IO won't solve it (as much as I like it and use it). The problem is simply your code is modeled wrong. If you need to mock a private method of your class for testing it, then that is the problem. - BTW, you may also consider looking into scenario testing as an alternative to unit testing which may help you avoid some of the refactoring: jducoeur.medium.com/… Commented Aug 16, 2024 at 13:40
  • Yeah, I know that my question not connected with IO (I even drop it from example), but with code structure. The reason I mentioned FP is simply to attract people who in my opinion have better chances to give good advices. And because I use scala. Commented Aug 16, 2024 at 14:17
  • Okay so the question then is, why do you need to mock private methods in order to test Controller? All should need to test Controller is a mock of ServiceA, if you want to follow traditional unit testing. Or you could use a different testing strategy. Commented Aug 16, 2024 at 15:12
  • I was concerned about chain of calls from Controller to Db, and to test any component from that chain one should mock it's dependent component. And mock of depending component seems nonoptimal strategy™ for me. So I wanted to look for alternatives designs Commented Aug 16, 2024 at 15:28
  • Why do you consider that "nonoptimial"? How would you do it in a non-FP code base? This is what I mean by the question being unrelated to FP. - Anyways, yeah do check the blogpost I shared about scenario testing, I have a feeling that aligns more with what you want. Commented Aug 16, 2024 at 16:22

1 Answer 1

5

Indeed, Dependency Injection makes everything impure, so that kind of object-oriented architecture doesn't work if you want to implement a functional architecture.

What to do instead depends on exactly which language you're using, and the idioms therein. In Haskell, for example, free monads may be one way to address such concerns, but while free monads are also technically possible in F#, I wouldn't consider them idiomatic in that context.

Since web applications are a kind of interactive software I usually find them well-suited for the Functional Core, Imperative Shell architecture. Another metaphor is to view that kind of architecture as a sandwich. Do impure actions, call a pure function, do some more impure actions, and exit.

Over the years, I've written quite extensively on this topic. As one example, other readers have found the following example useful: Refactoring registration flow to functional architecture.

In short, however, in functional programming (at least all examples I've seen), the entry point is always impure (cf. Haskell main actions), so you push all the impure stuff as close the entry point as possible, and then call pure functions with the values you collected from the impure actions.

Sign up to request clarification or add additional context in comments.

3 Comments

The idea of bringing impure functions to entry point sounds cool and simple. I guess it seems strange when one comes from some OOP experience when impure db for example is on lowest level of nested calls.
Take a look at this example with ZIO - blog.pierre-ricadat.com/… - similar approach can be used for Cats Effect: domain functions operating on case class/sealed/Scala 3 enum, errors handled with Eithers, some repositories working with an IO monad, controllers then translating between API/DTO representations and domain models, delegating domain operations to pure functions, and persistence to impure ones (IO monads).
The idea of a functional web app is simply having a Request -> Response function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.