by Philippe V., on 23 November 2016
Recently, one of our customers asked us to help them expand their market to a new country: France. Peek behind the scenes to see what we did and then check out the results in the case study for Listminut.
The ListMinut platform is a marketplace allowing people to find service providers for small jobs like babysitting, plumbing, or gardening. The location is therefore extremely important and they wanted the French users to browse a France-scoped site while keeping the current experience the same for the Belgian users.
They also wanted to keep a single administration panel and also not fork the codebase in order to easily fix bugs or make global changes in the future.
We had a meeting with them in order to plan the evolution of the app and prioritize steps based on the criticity of the task and the external constraints.
After some discussions we agreed with them on the following steps :
Some of these changes were structural ones while others were much simpler. Let's review the most interesting changes.
Obviously nothing can be done until we can tell if a request comes from France or Belgium or any other country the system might handle in the future.
Since the French users will access the website using www.listminut.fr and the current Belgian users will access it using www.listminut.be, the straightforward solution is to check the hostname and choose a country based on the TLD.
The problem with this approach is that it will only work in production. Whenever we are in dev, test, staging or any other environment than production we won't have a TLD at our disposal. We could use a trick on a development machine but it would not work for staging or ci environment.
Thus we decided to set a chain of places to lookup for a country hint and this chain would end with the TLD. Before that we would check for a custom cookie (easily settable with a browser plugin) or even a query_string param.
Technically speaking, we thought this solution was very close to the one of setting the locale of the request and added a
before_action in our
HasLocale controller concern. This also enabled us to choose the default locale depending on the country.
From this moment we were able to call
current_country from any controller and be sure to get a
This looked like a tricky one but ended up easier than we thought. Our first idea was to pass the request country down to the validation process like some kind of context. But something felt wrong and we realized going down that path would mean passing the country along in a lot of calls. Then we thought about storing the country in some kind of thread/request-specific variable (like
I18n.locale) but we didn't like that idea either because it is just a disguised constant in terms of dependency management; and actually most of us do not like how any piece of code can access
I18n.locale. Then we walked a step back and realised the country should be an associated record of our main object: a
User is deeply associated with a country (at least in this project), a
Worker also and so is a
Job and an
Address. Validating correctly a social security number, a bank account IBAN or a VAT number was just a matter of delegating the validation to the indirectly associated country.
The main problem was resolved but we were left with a more tricky one: countries were instances of a
Country class but different instances needed different implementation of the same method. The
belgium.verify_national_id_number(user, national_number) should be different than
france.verify_national_id_number(user, national_number) even if those two objects were of the same
This is a typical data vs. code problem and we solved it using the well-known strategy pattern.
Once users were associated with countries, we easily found solutions for the email URLs problem and the separate newsletter.
This one was probably the most interesting for us. The initial codebase was several years old and was authored by one senior developer and several trainees over the year. This meant that the categories were displayed on several parts of the webapps and almost every time in a different fashion. Most of the times the categories were even requested from the database directly in the template.
We solved the problem in 3 steps:
1. Create a PORO
CategoryTree representing a browsable (enumerable) tree of the category.
2. Use that
CategoryTree everywhere it could be used.
3. Add factory methods to build country-specific or level-specific (categories are either primary or secondary) trees.
It turned out to be very effective and even allowed us to fix some bugs and dramatically improve the performance on most pages because our tree was buildable in a pair of requests instead of N+1.
Since part of the app is built on angular 2, we also built a
The rest of the problems were either straightforward (change some controller actions by using the newly provided
current_country) or mostly non-technical (contact the payment gateway provider to propose payment mechanisms based on the country).
In the end we managed to do the expected work in a tight schedule (around one man month) and our client has been able to launch its service in Paris as expected.
It was also very interesting for us to make such a deep change in an external codebase. The biggest lesson we learnt (again) was not technical but organisational: the communication between the people involved (developers and business actors) was critical to achieve this success.
Thank you again ListMinut for your trust and we wish you the best in France !
Everything You Need to Know About Moving to a SaaS Model.
Get the guide now >