mtesseract / nakadi-client

Haskell Client Library for the Nakadi Event Broker
Other
13 stars 9 forks source link

Big API changes (replacing PR "Parameterize Monad") #59

Closed mtesseract closed 6 years ago

mtesseract commented 6 years ago

Foreword: This started as an attempt to simply modify the Config to be parameterized over some "base monad" b, effectively allowing the user to decide in which monads the provided callbacks are supposed to be run.

Since this change was not easy to be done in isolation, the whole type foundation of this package was reworked, now following a more MTL-style approach by defining monads MonadHttp, MonadHttpStream and MonadNakadi.

At the same time a goal was to decouble IO from this project as much as possible, allowing for easy IO-free mocking, for instance.

This required fundamental changes to the HttpBackend mechanism. The old mechanism was removed. Using custom HTTP backends is now to be done by implementing MonadNakadiHttp resp. MonadNakadiHttpStream for a custom monad.

A general hindrance for this API redesigning was the rather cumbersome API for event consumption. This is why, at the same time, these APIs were completely reworked, now exposing a much simpler interface to the user.

Since these building lots (type foundation, complex event consumption API, configuration API, IO decoupling and HttpBackend mocking) were all interconnected tightly, this PR now became bigger than anticipated.

Summary of the changes:

@etorreborre Sorry it became that big.

etorreborre commented 6 years ago

Thanks for the summary, I will try to have a fresh look at this PR next week but more importantly do a branch of our app with those changes to see if everything works as expected in terms of API and runtime behaviour.

mtesseract commented 6 years ago

FYI, @pascalh.

mtesseract commented 6 years ago

I have just added a convenience layer for easy mocking of http backends. @etorreborre

etorreborre commented 6 years ago

Thanks a lot @mtesseract I'll try to give it a go today!

mtesseract commented 6 years ago

Update: After this design journey I was finally able to save the best of both worlds: (1) The easy mocking API based on the idea of storing an HttpBackend directly in the Config and (2) the decoupling of IO. For this I had to insert some more MonadMask constraints, but this is totally fine in my opinion.

The MonadNakadiHttp based solution prepared the rest of the code base for the essential distinction between IO and non-IO (for example, the class methods did not leak out any IO types anymore). For some reason I have not seen this solution before. :-( With this in place, this modification was rather short.

Thanks for your feedback / your criticism with respet to getting this right. I appreciate it! And I'm sorry if you wasted too much time on the MonadNakadiHttp thing,

etorreborre commented 6 years ago

No worries, it is great that we can finally end up on this solution, I will give it a go on Monday (I meetings permit...).

etorreborre commented 6 years ago

I am almost done on using this branch in sparrow, some tests are not passing but I think it's my fault. One request, is it possible to add an instance of MonadNakadi for WriterT m? (I'm currently using StateT m as a workaround).

reactormonk commented 6 years ago

You should be able to cut down on the boilerplate of writing mtl instances via https://lexi-lambda.github.io/blog/2017/04/28/lifts-for-free-making-mtl-typeclasses-derivable/

mtesseract commented 6 years ago

I have implemented and integrated the MonadNakadiBase monad — without incoherent instances, but with some well-defined overlapping semantics.

MonadNakadi b m now requires MonadNakadiBase b m. It encodes the required lifting functionality.

nakadi-client provides MonadNakadiBase instances for several instances, e.g. IO and ReaderT r IO. In case a user has some very specific monad transformer stack set as Nakadi base monad, that can also be used. Either by defining a MonadNakadiBase instance (no methods need to be implenented) or using the included runNakadiBaseT wrapper (does not require additional instances). There's a test that demonstrates this.

With this in place, it works out of the box to do something like runLoggingT . runNakadiT conf, something that didn't work previously.

Keeping MonadNakadi and MonadNakadiBase seperate simplifies the code significantly. For the most common use cases the user does not have to do anything, it just works.

I have also simplified MTL deriving somewhat.

mtesseract commented 6 years ago

@etorreborre I have force-pushed onto this branch in order to clean up the history. Feel free to review. If you have questions, please ask. :)

etorreborre commented 6 years ago

That looks really good to me. Can't wait to upgrade :-).

mtesseract commented 6 years ago

So, I take it, you are fine with merging? As I said, there is more to come. I would simply be very happy about being able to continue with much smaller PRs. :-)

etorreborre commented 6 years ago

Yes let's merge.