My team is currently estimating an effort to move to a microservice-based architecture. We are evaluating Elm as our primary front-end technology. We have teams organized around the domains we serve and each team will manage UIs for both internal and external users.
I’m currently coming to an approach involving having multiple Elm apps embedded on a single page with a web worker to help handle communication and lazy loading of bundles. I’m curious if anyone else is currently using Elm in a microservices architecture like this, and if so can you share any details on your setup?
I understand that the backend will be microservices, but that fact alone would not force you to reproduce the architecture on the frontend, right?
Of course only you team can decide if it is an option, but if you used only one app on the frontend you could gain typesafety between the services.
It’s fine to use microservices on the backend, but – especially because you say you have external users – it’s super important to have a unified API that hides the fragmented implementation.
In other words, UI programers shouldn’t care whether a backend is a monolith or a microservice suite.
A practical way to do this is to have an API gateway that handles all requests, routing them to the correct microservice as necessary. This can be a simple (ish) router if any one request is handled by one microservice. If your setup is more complicated than that, have a look at graphQL, which can resolve different sections of the response separately. (GraphQL is also worth a look if having an API schema with types appeals to you.)
So, having multiple small apps on the page isn’t something Elm is good at, and if your API is unified and consistent, not something you should have to do anyway.
How you want to approach Microservice Frontends depends on your bounded contexts and how big your application really is. I’m aware that you were asking for explicit technical advice but I would like to share some experience with the organizational side of things.
Keep it simple - is a monolith sufficient for now?
Using Elm, you will have some unique advantages since refactoring is so easy. The following is probably not what I’d do with anything else than Elm. What I would do if I had the task to put Elm in front of a backend microservice architecture:
Build everything into a monolith, one repo, one Main.elm, everybody commits to this, maybe compartmentalize your bounded contexts into subdirectories. Maybe have multiple entrypoints for completely disjunct regions of your page. The elm-spa-example should get you pretty far. This has many nice properties:
PRO: A single deployment pipeline, deploy often and nobody will be scared to deploy
PRO: A whole lot of team synchronization regarding frontend code could be handled by Git - given that you use trunk-based development, feature branches are evil , feature toggles rule. Nothing slows you down more than inter-team-communication but this again may depend on Conway’s Law
PRO: Delivering a consistent look&feel is way easier in a monolith
PRO: A single artifact, no duplication, no message brokers, no inter-module communication crossing application boundaries
PRO: Elm 0.19 will be optimized for this kind of usage, handling assets should be a breeze
PRO: Polyfilling your environment will be global, your app won’t suddenly break in IE11 when your users arrive at the last stage of the purchase process
CON: You have to keep a keen eye on coupling, using modules from another bounded context might not be OK, you need to make sure the teams are on the same page. I’d establish rules and try to automate enforcing them with a precommit hook.
CON: You will have to decide whether the bounded contexts use the same infrastructure pieces (i.e. how to obtain access tokens), I’d advise against that if you don’t have a dedicated team for this. If you do, good for you
Measure if the application size is actually a problem for users.
Define “too big”, “response times that are too long”, automate the checks that run on every build, maybe have a traffic light widget that warns you if you enter the yellow zone
Maybe build a simple Kibana Dashboard to monitor this continuously? You are monitoring your microservices, right?
Sentry is pretty nice for capturing stuff in the UI, with pure Elm you will probably rarely need this but you will need some JS that is bound to break at some point
Measure if all frontend teams working on the same application is really a problem.
Are people frustrated that the other team constantly breaks your code? It’s always the other team right?
Is the monolith impacting your teams’ velocities? Measure that!
Measure if the application size is actually a problem for users, consider adding entrypoints for parts of the application that don’t have to communicate with the main app (i.e. an admin frontend)
If there is no problem right now just iterate on the monolith
If you have a real case for physically splitting up the artifact, proceed to the next section
The next section
Think again if you really want to do this, I’ll wait…
Still here? My guess is that you will arrive at this stage way earlier with anything other than Elm. I might be retreading what everybody already knows but I’ll take that chance .
Some context
At work we have a rather large React monolith that is in the process of dissolving into single pages. These are split into the same vertical slices that represent the team boundaries - so say Sales gets its own “sales.awesome.app”, CRM “crm.awesome.app”, you get the idea. To me it sounds like your team per domain setup. This is not what I would have done personally because there are usually core components like headers/footers/sidebars/carousels/… everybody needs/wants to use and who is going to own these when your team/ownership boundaries are strictly vertical?
In my experience ownership and team stability are the key ingredients. Conway’s Law is in full action here:
If your boundaries are problematic, you’ll struggle when you need to cross boundaries
If your boundaries are aligned on teams and your teams are instable, you’ll struggle because the boundaries are constantly shifting
If you have vertical boundaries but you need partials - what Facebook calls Pagelets, aka SmartComponents - that nobody wants to own, you’ll struggle to get things done and people will blame the other team
General organizational advice
At this point you have already decided that it isn’t feasible to maintain the monolith. Now is the time to let lose of your earthly possessions. Treat every widget as a black box, the fact that they may all be implemented using Elm is incidental. There will be some widget where using Elm just doesn’t make sense, the team should be able to make the decision to just roll with vanilla JS, if necessary. Or maybe you have that awesome WASM tech demo? This also means that you should design your widgets in a way that assumes the minimal amount of global knowledge. Keep in mind that:
CON: keeping your UX/UI consistent will greatly depend on team communication and synchronization.
CON: the individual widget size might increase, caching is advisable.
CON: synchronization of commonly used views with/without having a platform team will depend greatly on team communication
CON: deployment will be harder since you’re probably dealing with more than one pipeline
CON: shared infrastructure stuff will be even harder to manage
CON: teams tend to silo themselves, you may need to counter that with more communication
CON: polyfilling the environment and managing overlap gets tedious at times
but it’s not all bad
PRO: coupling will be way harder than just keeping to yourself
It’s still not too late to turn back
a) Based on that: have one way to compose your widgets. WebComponents work but maybe <iframe>s are a good solution too? I have used a simple <script id="put-that-widget-here" src="https://team.app.org/fragments/some-widget.js?id=this-widget&where=put-that-widget-here"></script> approach with little friction. Depends on what you want to do. Make sure that this is known and agreed upon.
b) Avoid having “common” code, if everybody owns it nobody owns it. This applies to the infrastructure code like the lazy loading mechanism you mentioned in particular, this is a critical part of your site and needs to be owned by a team that is committed to maintaining it.
c) Don’t build big things. That webworker thingy sounds complicated, maybe the team responsible for the header just putting in a <script id="put-the-cart-here" data-some-attr="some-value" src="https://shopping.awesome.app/fragments/v1/cart.js?id=header-cart&where=put-the-cart-here"></script> is totally sufficient?
d) Compose widgets. Maybe the header is owned by team A but the shopping cart widget in there belongs to team B, let this widget be self-sufficient so team A just has to include that with the minimum amount of integration work - pass props down, dispatch events on the document and subscribe to the topics on the document you’re interested in. Namespacing topic IDs may be advisable. The widget should know where to get/send its data, little need for external configuration.
e) Communication between these widgets should be minimal. You might be tempted to write a message bus for your frontend… are you sure document.dispatchEvent(new CustomEvent('shopping.cart.someEvent', { detail: { someData } }); and pumping that through a port doesn’t do the job? You can also use window.postMessage but then you need to watch out for origin issues.
f) Some organizations like Spotify have a platform team that supplies all the base widgets/view functions/infrastructure/whatever but that might turn into a bottle-neck that blocks your teams’ progress. Maybe it’s the best option because special widgets are rarely needed or they’re really fast at doing what they do? At a certain point they may be in maintenance mode and every team can simply choose from the building blocks that their kitchen sink provides. I’m not sure how this would work with Elm stuff, I tend to like just having a design system like Salesforce’s Lightning Design System - not affiliated - with a kitchen sink demonstrating how to use it and just let the teams roll their own logic.
That’s my .2c that is light on technical advice but as you probably guessed by now: good team communication and sound organizational boundaries happen to be way more important than your technical choice of how to lazy load your widgets. Feel free to ignore all that if your organization is already awesome. Hope this helps!
Resources that might be interesting that you’ve probably discovered by yourself: