Best practices and abstractions
See original GitHub issueWhen we’ve started to use finch (from v0.5) as a basis for our REST/HTTP API we’ve struggled with a lack of some practices, guidelines and how-to about abstractions on top of underlying Service[HttpRequest, HttpResponse]
.
Finch by itself is a pretty low level layer which responds to work with HTTP requests/responses with some neat monadic extensions/ways to do. And nothing more. Finatra, for example, was built around Filter/Controller
and its composition.
So we decided to abstract it on our own.
Now our simple CRUD controller looks like that:
class ItemsApiController(implicit val injector: Injector) extends Controller {
private val itemsService = inject[ItemsService] //We use scaldi as DI
private val itemReader = body.as[Item]
def findItemById(itemId: Long): Action = securedAction { reqContext =>
itemsService findItemById itemId
}
def userItems: Action = securedAction(pageReader) { page => implicit reqContext =>
itemsService.userItems(user.id.get, PageRequest(page))
}
def newItem: Action = securedAction(itemReader){ item => implicit reqContext =>
itemsService.newItem(user.id.get, item)
}
def updateItem(itemId: Long): Action = securedAction(itemReader) { item => implicit reqContext =>
itemsService.updateItem(user.id.get, itemId, item)
}
def removeItem(itemId: Long): Action = securedAction { implicit reqContext =>
itemsService.removeItem(user.id.get, itemId)
}
override def routes: Endpoint[HttpRequest, HttpResponse] = {
(Get / "api" / "items" /> userItems) |
(Get / "api" / "items" / long /> findItemById) |
(Post / "api" / "items" /> newItem) |
(Put / "api" / "items" / long /> updateItem) |
(Delete / "api" / "items" / long /> removeItem)
}
}
Looks pretty intuitive and simple as for me.
You may ask why do we ever need a controller layer when it’s just a kind of proxy to services? Well, it’s a good place to do lazy authentication, check user permissions, validate input and many other things which are not welcome in a real business logic. Yet it provides a simple way to compose all controllers together and bind it to HTTP port.
So here is a question. Do we ever need this bicycle-boilerplate as a part of some finch ecosystem, some simple example & bootstrap for newbies?
I completely understand that this abstraction level should not be a part of finch core just because of its nature. I just wanna know if someone interested in that experience and how should I present it in that case - some article or even finch-subproject
.
Issue Analytics
- State:
- Created 8 years ago
- Reactions:3
- Comments:10
Top GitHub Comments
Thanks @ImLiar!
This is a very very good question. And I believe everybody who is using Finch nowadays faced the same question. We’ve discussed this at lot, and I remember your participation in early discussions. Here is the short story.
The was a ticket #204 “Finch in Action” about the same question - how to write services (organize your code) with Finch. We tried to come up with something that answers this question and the
Micro
-thing showed up. Our goal was to hide the HTTP types and be more high-level. But it turned out that it wasn’t a good idea since it didn’t represent the whole picture, which is very complicated. So it’s deprecated right now.I personally believe that Future Finch with coproduct routers (see #221) merged with request readers into something like
Endpoint
(see #254) is an answer to your question. Having endpoints, we can say that it’s idiomatic to have a bunch of endpoints (representing microservices) that implement your business logic.That’s being said, I hope we can go in that direction and be microservices-oriented thereby merging two layers (services and controllers) into a single layer of microservices that implement “open-closed” principle: open for reuse but closed for modification.
With such toolchain, one can build a system that follow any possible style (i.e., CRUD). And my personal understanding that Finch should stay in library-scope so its users may be innovative in the approaches they use to organize a code around services. But this doesn’t mean that everybody should invent his/her own style (backed micro-library) to work with Finch. We will provide a basic schema based on endpoints (see example above) so one can start with it and then wrap them with any number of abstractions (like controllers) he/her feel comfortable with.
Quick example. Here is how the first ever Finch 0.1.0 code looked like. I also asked this question to myself 1 year ago and come up with that silly idea of extendable and composable services. And it worked great for me. As for today, I would probably stay within endpoints mentioned earlier.
The bottom line is I’m doubtful about having this approach merged in Finch. But it might be a good idea to have it as a separate project or at least post a blog about it: this is very very valuable experience that worth sharing with the community.
@ImLiar can we make a deal? Let’s wait until endpoints are shipped (0.8 - 0.9) so you can play with them. I’m quite sure you will be happy to replace two layers of services and controllers with just a single layer or reusable endpoints. If it won’t work for you, we will think about CRUD-style extension for Finch exposed as sub-project (maybe
finch-crud
).Although, it’s not my jurisdiction to stop you from shipping this as a separate project in the Finagle organization. So you can totally work with @travisbrown on this direction.
Thanks @ImLiar! Feel free to open a PR within a simplified version of CRUD-style controllers - I think it would be super helpful for newcomers. Also, having this example live in the same repo as finch-core will ensure that it’s always aligned with the latest API.