I've had the same conversation now multiple times, and that tells me it is probably time to type this up and share it with every social media site I write for. In this case I want to share my perspective on the microservice-ness of a UI.
I'll just state it:
Your website should be treated as a first class Microservice. Don't let anyone tell you otherwise.
I posit that there isn't actually anything different than a service that returns RESTful JSON via carefully crafted HTTPS url routes and a service that returns RESTful HTML via carefully crafted HTTPS url routes. Unless you are claiming that microservices need to return JSON, then I guess that would also rule out the whole benefit of having
Content-Type headers in the first place (so of course you aren't going to make that argument).
The core importance is the decoupled nature, and the fundamental truth is that most websites today are not microservices. That is because they are usually strongly coupled to some "back-end service". As a quick digression, I really dislike that word, almost as much as I dislike the "front-end" one. In reality the only difference is that your users are using the "front-end" described in a particular language and displayed in a special rendering engine. Did I mean
HTML and your browser, or
OpenAPI specification and
Swagger? The similarities between the
site-map and the
root basepath are astounding.
As such you should treat your UIs as first class citizens. This means providing good RESTful urls for your users. They shouldn't need to change some characters in the url to ensure they get to the right place. The necessary tokens are contained there so they never will see a screen like This is a form resubmission, are you sure you want to do that? If that shows up you know someone violated REST. In rest you would be able to make that request and get back an 409 error suggesting it was already submitted. (To the well-trained eye, you might say, what if they made a continuation request and got back a Pre-Condition Failed? Sure you're right, but I don't remember last time I saw an API that implemented that correctly and there are far more instances of "resubmission alerts" then there are of correctly implemented RFC standards).
To go along with this I recently had a conversation on a merge request and was met with this comment:
I think it'd be nice to avoid URLs like
team?next=WW91clRlYW1pbmF0b3I%3D, they look enigmatic and not very friendly.
Well they certainly do, but so does
https://service.com/v1/route-path/6ed669b0-f8a4-48c0-9646-f04212f7e00d/sub-resources?q=search-string&s=more-order, right? I mean actually would want the user to be less inclined to change the url than the one in the service. At least in the service, I might have provided some non-RESTful documentation explaining what the
q and the
s query string parameters actually do.
There is a combination of what experience do we want to provide and what tools do we have to do it. Let's say we started to violate REST a little bit (just a little) and started to imply to the user what the internal semantics of that opaque url are and let them understand a bit about the application.
You can argue that there are some cases where data lies on the boundary of application logic and something that doesn't hurt the user to know about. However, in REST the paradigm is that the urls are sacred opaque strings that are NOT interpretable. We should take that to heart, providing an error message which gets passed along in the url is just not a good pattern, for instance:
- We decide as developers that the internal name for a route is
- We decide to tell our users it is the "Welcome" screen
This actually is fantastic, we have a decoupling that doesn't imply that these have to be the same. Now there is a disconnect between these two, and trying to make all our internal names match the expectations is a burden and unnecessary tight coupling between the data/routes and the display of that information. I.e. every time we change the title page, we need to change all the references to that. Instead you could have imagined that we call the route
Welcome as well. Now when we display that route you get something like
https://route/welcome, what happens when you translate your page into German, do you also provide the
https://route/guten-tag. I'm going to go out on a limb here, and say of course not. And that is because the rendering of the data in the route is separate from the internal naming that we use. Furthermore what happens if the user switches languages, do we redirect them to a different route? We'll sure, but I would suggest
https://en.route/gutan-tag, wouldn't that be weird to see!
Actually while developing Teaminator, I found we actually did have a similar case. We move our "Home" screen around frequently as we experiment. We still have tons of pointers to
Quests even though there is no exclusive
Quests screen anymore. Actually it turns out that is a really good idea. Because it means if we ever decide to split the Quests screen from the now
Home screen which is
Your Teaminator, everything is already pointing at the right place. And right now there is the expectation that they are the same screen, and that is done via a redirect in the router, rather than requiring all the route names to match to the usage of them. Navigate to
Quests always works, and Navigation to
Home always works, and they are just right now happen to be the same.
There are some rare cases where we do need to tell the user where they are and provide them easy to navigate paths, but these are also not unique to UIs.
- If they want to go to Teaminator => What is the url they can type into the browser.
- The corollary to this is, if they want to understand all the top level routes of your service, where do they go?
- They are on a route /team, this should match the route they see in the UI, i.e. wouldn't it be bad if you were on the
/error-pageurl and the web page you saw
Welcome to Teaminator.
Actually even in the latter case
/team should also be abstracted away.
But we could have something so much prettier for our user.
I'm going to use the ridiculous extreme here to make the point. An alternative way of looking at this is like asking for our quest service to return a
questId that is human readable, can we do that, of course, but it doesn't really make sense. The user doesn't need to know or care what the
questId is and it creates an unnecessary burden on the service to provide those and ensure they make sense. Not to mention the coupling of the description/title of the quest to the questId, you can't change one without having a discrepancy. I.e. if the quest is "Reomve Distractions" do you make the quest name
remove-distractions or `reomve-distractions (yes the point is the spelling mistake)?
Today, the UIs that my teams build have decoupled deployment and versioning from the microservices they rely on. There are no dedicated services specifically there to directly support the UI. In fact, any service needed to help support the UI is frequently reused to support direct API integration or another UI. Try it out if you want, you can go to any of Teaminator services and do you quests directly.