Design decisions in a time-constrained scenario
Back in mid-2020, the Líbere product team was being built under a clear company vision and a greenfield ahead of us. Líbere Vitoria opening was coming up in a few months, and that meant going from 0 to 120 operated apartments. The clock was ticking!
As part of our autonomous model, providing advanced, flexible, and secure access control to our spaces was a must from the very beginning. We equipped all doors with cloud-based smart locks and we were ready for our very first MVP.
Having in mind that the long-term vision was to automatically grant access based on reservation details (check-in / check-out), we reduced the scope of the MVP to a manual solution: a back-office tool for our agents. Was this solution scalable? nope, but time to market was critical and we had so many things to validate first.
Furthermore, guests would need a private area (My Place) to check their PIN and unlock the doors. Our MVP specs were ready: we needed two separate front-end apps for two different userbases (guests and internal agents).
The initial approach
When we were deciding how to build the Backoffice and My Place, we already had a service written in Go which was responsible for connecting with the smart locks. We were pretty sure that Go would be our main programming language for our backend, but that didn't mean it should be used everywhere without exception.
The initial chosen stack for the MVP was composed by Spring Boot, Thymeleaf, Bootstrap, and jQuery (yes, jQuery in 2020). Why a server-side rendered approach instead of a client-side or SPA?
- Agility: we were in a rush, and relying on well-known tools would help us meet the deadline.
- Lack of front-end expertise: we knew we weren't building the foundations of our front-end stack, and whatever we decided to build would need to be rebuilt almost from scratch.
- Rely on tools we were confortable with: related to the above, using Spring Boot would make our development faster and with less uncertainty. The benefits of using a solid framework are well known…
Having started with this approach allowed us to postpone some decisions regarding services responsibilities and architecture. At the early stages of almost every project, it is usually hard to have a clear idea of what the overall architecture will look like in the long term. But we reached a point where things were getting clear, and more boundaries were being identified.
At that point we had two main issues:
- The obvious: not having a proper separation of front-end and back-end made it much harder to keep UI consistent, not to mention starting to apply the upcoming design system.
- By having two back-end languages (Go and Java), we had to maintain their corresponding common components for logging, DB access, event bus, and so on. Replicating the same work wasn't ideal.
At the beginning of 2021, Gorka joined the flock and brought a lot of front-end expertise. It was time to welcome React to our stack and travel back to the present in terms of front-end stack.
This was the perfect time for us to define our backend for frontends (BFF). We also call them gateways, and we take them being the entry point to our system. The existing Spring services will turn into BFFs getting rid of the UI part. Their responsibilities will be mostly limited to data user authorization and data aggregation.
Transitioning to this approach involved several changes:
- Services APIfication: this was the first step towards enabling support for React apps. The Spring services will define API endpoints and we will end up removing the ones that were serving Thymeleaf based HTML views.
- New gateways definition (in Go): we took the opportunity to rewrite those gateways from scratch in Go. Rewriting may be considered as a bad symptom (who wants to do the same thing twice?). For us this was not accidental but expected for the mid term.
- Ensuring migration correctness: because of the above, there would be some time where both Spring and Go services will be running at the same time. It was critical that the behaviour of the most important endpoints didn't change. In order to ensure that we were migrating properly, we used a kind of simplified Strangler Pattern to compare responses from both services.
After almost two years, the way we understand the business and the requirements has changed completely.
Did it make sense to spend time and effort on building something that will end up being rewritten from scratch? For us it did mainly for several reasons:
- We were aware of the purpose of the first approach, and we didn't put effort on anything that would not bring real value.
- The business context where a hard deadline for the first building opening was planned for a specific date.
- The lack of front-end expertise at the beginning.
So rewriting doesn't mean failure. Rewriting may be worth sometimes, it may be preferred to rewrite rather than extending technical debt for a long time.
Would we follow the same approach? This is not easy to answer because all of us evolve over time. I personally think that if we were in the same situation, we would have taken a mixed approach. We would have avoided using Java (we are not against Java, it is just a matter of having an unnecessary diversity) which required us to maintain two separate libraries. Instead we could go with a Go service with a different template system for the views, or an SPA served from the same service.
At Líbere, growth is present on the DNA of the company. The ambition is almost infinite (we want to be the biggest alternative hotel chain in Europe). Scalability is intrinsic to the company values: more buildings -> more apartments -> more inhabitants.
If you like what we do, you're lucky because we're hiring!