I can only explain it as lock-down madness, but a couple of weeks ago I decided to have a little play around with Vapor.
What I wanted to do, initially, was just make a simple website that did user authentication. You could register, login, and logout. If you were logged in, it knew who you were. If you were logged out, there were things you couldn’t see.
Now I’m no web developer. Admittedly I did write a WYSIWYG html editor in Hypercard, in about 1994, but I’m no web developer.
Ok, I might have also written a complete CMS using Hypercard as a CGI engine for MacHTTP, also around that kind of time, but honestly, I’m no web developer.
If really pressed, I might admit to having had a job creating the first interactive shopping basket for Robert Fripp’s DGM record label’s website in about 19981 - a job which I had to learn Perl for2 - but if that goes to prove anything, it is that I really am not a web developer.
Still, how hard could it be, right?
So actually, it’s kind of not that hard, but…
It turns out that the latest version of Vapor - Vapor 4.0 - is quite new. New enough that the documentation isn’t finished, and there aren’t many tutorials or examples out there.
It also turns out that the authentication examples which do exist for Vapor 4.0, and indeed most of the examples for Vapor 3.0, focus on making API back-ends. Which is understandable, but not that helpful to me.
Add to this mix the fact that Vapor 3.0 is different enough from Vapor 4.0 that it will confuse a noob like me who is just copying & pasting things to find out how they work.
Finally, add to this the fact that Vapor is based on SwiftNIO, which has a Combine/Reactive-like declarative syntax for its networking operations, and a certain amount of bafflement ensues.
I think I’ve puzzled my way through it by now. I cannot offer up any great insights, but I will offer up the results of my labours in the hope that they might be a useful starting point for someone else. To be clear: this is still very much work in progress, and in any case, I’m only tinkering.
From my brief time with the framework (less than two weeks, during which time I’ve also been doing other things), I have to say that my impressions of Vapor are very favourable.
It seems pretty well thought out, nicely structured, and not too complicated. It’s just a shame that there wasn’t a ready-made template that fit my needs.
I’m still slightly less convinced about the whole SwiftNIO/Combine/React thing, as this Twitter thread illustrates.
It’s not that I think the basic approach of declarative-ness, and futures and compile-time checking of the information flow is wrong.
Just that I’m not convinced about the API naming, and I’m really not convinced about the readability of the resulting code, or the ability it gives one to reason about it.
I’m not sure that using language drawn from functional programming for the API operations is helpful. It’s technically correct, but I find it confusing when the problem domain I’m thinking about is really a stream or pipeline of “events”, or maybe even “network operations” or “database objects” - something like that anyway. It does not feel natural to me to describe this as a sequence of
flatMap etc operations (even though that’s what they may be).
I’m also not sure that the clever type inference that Swift can do in these situations is always that helpful.
I mean, clearly it’s useful in some ways. It’s great to be able to statically check that a pipeline of asynchronous operations are passing along the right type of data to each other, and that a known number of outputs or error conditions can result.
It bloody confusing though when Swift gets confused. Usually this is because you got confused, but the resulting error is often cryptic, and pops out at an unexpected part of the chain. This is not a new problem, as anyone who has worked with SwiftUI, or indeed has any kind of shady past involved template meta-programming in C++ can attest.
Even when things don’t go wrong, as a reader of the code you end up with, it’s often not at all apparent what the underlying type is that’s being transferred from one closure to the next.
It’s not even that apparent sometimes if something is a futures, or an actual known values, and quite what the sequence of execution will be. Maybe my brain is still too used to procedural code.
There also seems to be a common pattern where you end up creating a nested sequence of futures inside a closure, only to then transform the result of the last one back into the input you got at the beginning - in order to ignore a few intermediate results from the pipeline and preserve a value from earlier so that you can forward it to a later part of the pipeline. There may be a clean way to generalise this pattern, but in quite a few of the examples I came across it’s just written out in long-hand, which makes it pretty hard to parse what’s going on if you’re just reading someone else’s code.
All in all I’d say that I like some of it, need convincing about some of it.
I also find myself wondering if this is a job for some funky custom operators (oh god…), or even some sort of domain specific language (oh god oh god oh god).
Either of these could make the code a lot clearer to follow. Or make it even harder to reason about and fuck everything up terminally.
Luckily, and I should probably emphasise this, I’m not a web developer :)