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?