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?
IOwon'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 aprivatemethod 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/…Controller? All should need to testControlleris a mock ofServiceA, if you want to follow traditional unit testing. Or you could use a different testing strategy.ControllertoDb, and to test any component from that chain one should mock it's dependent component. And mock of depending component seemsnonoptimal strategy™for me. So I wanted to look for alternatives designs