Photo by S O C I A L . C U T on Unsplash

Serverless Internationalisation 🚀

Example of building our serverless solutions once and with extensibility so they can be consumed in different locales safely, written in the AWS CDK and Typescript.

Introduction 👋

When we work for enterprise organisations we are typically building serverless solutions which could be consumed in multiple countries and locales; however what we don’t want is each country needing to build their own versions, a complete rewrite in order for a country to consume it, and we don’t also want code bases to diverge over time when they are reusable.

“what we don’t want is each country needing to build their own versions”

The code repo can be found here: https://github.com/leegilmorecode/serverless-internationalisation

⚠️ Note: The CDK and code examples are to explain the concepts only and are not production ready.

Before we go any further — please connect with me on LinkedIn for future blog posts and Serverless news:

This article will cover a high level example approach with accompanying code, which shows how we can build once, but have extension points which allows other countries/locales to safely change the solution for things like:

✔️ Schemas. Different data schemas, for example postCode (UK) vs zipCode (US). Taking an ‘Employee’ example, National Insurance number (UK) vs Social Security Number (US).

✔️ Validation. Different regex validations per locale based on schemas, again with the example of postCode vs zipCode vs others... This could be validating data, events, or API requests for example.

✔️ Messages. Return values for consumers of our APIs (which language should this be? What tone or message?).

✔️ Logging. We may have a requirement to log based on different languages.

✔️ Business Logic. There will always be business logic differences per country, even if only slight changes over a generic global service. We cant always keep business requirements and processes global due to areas like legislation, for example.

What is Internationalisation?

Lets very quickly cover the meaning of Internationalisation, and how it fits into a wider concept called Globalisation.

  1. Translation (T9N)
  2. Localisation (L10N)
  3. Internationalisation (I18N)
  4. Globalisation (G11N)

Translation (T9N)

Translation simply means rendering text or speech from one human language into another. This is typically done using keyed JSON files for multiple languages.

Localisation (L10N)

Localisation is the process of adapting applications and text to enable their usability in a particular cultural or linguistic market.

Internationalisation (I18N)

Internationalisation is the design and development of a product, application or document content that enables easy localisation for target audiences that vary in culture, region, or language. Internationalised solutions provide a great user experience for people everywhere

Globalisation (G11N)

Globalisation tackles all the legal, logistical and practical aspects of working in new markets, including market research, product design, making contacts in new markets, developing market-entry strategies and locale-specific content strategies, exploring legal and HR issues and customer retention and growth once established.

Let’s look at some visual examples 🎨

Often the expectation when we build our solutions is that they will be automatically reusable for all other locales, as shown below:

You can see from the diagram above that in a fictitious e-commerce example, our expectations are:

  1. All business logic is exactly the same across all of our countries.
  2. We have an expectation that other countries can consume without needing to make any changes.
  3. We presume that subsequent new features, for example ‘Track my order’ can just make its way to other countries and consumed.

The reality however is often very different in my experience which is shown below:

In the example above we can see that:

  1. All business logic and requirements are not exactly the same (which is common sense in reality), and the code/solution can not be used ‘as is’ without major refactoring or rework. Sometimes down to legislation reasons for example a ‘one size fits all’ approach just doesn’t work…
  2. Non-global requirements coming in cause divergence in code across all of the different countries. As businesses are always pushing for new features to underpin revenue this happens a lot.
  3. New global features can’t be merged across into other countries as the code bases have diverged, and there is no way to merge the changes in..

Sometimes down to legislation reasons for example a ‘one size fits all’ approach just doesn’t work…

An example of this which was discussed above is different data schemas per country, as shown below in this simple example:

If we build with UK specific schemas then what does this mean for reuse with other locales and countries? Lets look at what this means for business logic requirements too:

We can see in the example above that:

  1. We have some differences in business logic between locales which can be easily amended based on config. These are the simple ones to deal with.
  2. We have certain requirements that are only specific to certain countries, and not required in others.
  3. We have some requirements where we need to make subtle differences, perhaps sending an email instead of a text message (SMS).

⚠️ Note: With this approach it is imperative global changes are communicated with other countries and that there are no breaking changes.

Let’s look at how we can work around all of this with the AWS CDK in the next section.

The approach as a concept 📐

One approach to this is using a folder structure and locale specific overrides at build time using the AWS CDK which allows us to create a ‘Global’ version of the solution, and other specific locales to override in their own specific folders safely; allowing each country to:

✔️ Swap out country specific validation, data schemas, return messages, logging etc etc…

✔️ Override IaC properties for specific countries (perhaps one locale needs reserved concurrency on a Lambda or a difference in memory)

✔️ Override Lambda code (or equivalent, say Step Functions) where you need to, but use the base Global version if not changes are required.

✔️ Code never diverges and we can work from one repo.

This allows country specific teams to only make the override modifications where they need too, with maximum flexibility. If the Global version works for the specific country then we don’t need to override the file at build time.

Folder Structure 📁

With this approach we use a folder structure which is shown below:

Example folder structure for Internationalisation

We can see that in our example we have three countries, ‘Global’, ‘UK’ and ‘US’. Global is the ‘base’ for all countries to work from, and can easily be overridden when/if required.

“If an override file exists for that locale then use it, if not fall back to the Global version.”

An example of this is the code for the ‘create-sale’ Lambda which has a Global version, but it has also been overridden from a UK perspective. At build time if we build for the UK version then we utilise this file over the Global version i.e…

If an override file exists for that locale then use it, if not fall back to the Global version.

Overriding files based on locale 📂

When we deploy from a CDK perspective we have various NPM scripts, each of which has a locale specific version as shown below:

This means at build time from a CDK perspective we pass through that locale environment variable which is used to determine which files to use.

If we then look where we instantiate our Stack in our ./bin/sales.ts file, we pull in the locale specific IaC props at build time using a function called ‘dynamicImport’:

The dynamicImport function is shown below, alongside a dynamicPath function which is utilised when it comes to the entry point of the Lambda function later:

You can see that in this crude example the correct file is imported at build time based on the locale which is being set at build time, but if no override version exists (because the functionality or file is Global in nature) then that is returned instead.

If we then look at our sales-stack.ts file we can see these props being used at build time depending on the locale:

This is very powerful as we can now safely change our IaC props per locale being built for. The important thing around this is heavy automated testing to ensure everything is being built correctly per locale.

“This is very powerful as we can now safely change our IaC props per locale being built for.”

If we then look at the entry point for the Lambda’s, we can see that these can also be swapped out per locale at build time using our dynamicPath function we discussed earlier:

Again this is very powerful as an approach as we can override the Lambda code as an extension point per locale if there are differences in behaviour.

“Again this is very powerful as an approach as we can override the Lambda code as an extension point per locale if there are differences in behaviour.”

If we look at the code itself for the create-sale Lambda, we can see that the locale specific error messages and schemas are pulled in at build time:

This means that per country or locale we can change the data structures, error messages, logging requirements… essentially anything at build time where it makes sense to do so! This is also the extension point from a business logic perspective where we can have differences per locale.

“This means that per country or locale we can change the data structures, error messages, logging requirements… essentially anything at build time where it makes sense to do so!”

A quick example of how the locale specific error messages works is shown below:

This means at build time we will have returned the correct locale specific error messages with placeholders, which can then be utilised using a string format function as shown below:

// throw random error, this has the correct translated values per locale which you will see in the return value from api gatewayconst errorMessage = stringFormat(errorMessages.notFound, "Sales");

We do the same from a validation perspective by pulling through the correct ‘create sale’ payload schema based on locale, and utilised as so:

// we validate the payload of the UK sale objectvalidate(sale, schema);

At build time again, we pull in the correct schema object, with an example shown below:

This is very powerful as at build time we can have specific regexes for specific locales for our API and Data validation needs:

“This is very powerful as at build time we can have specific regexes for specific locales for our API and Data validation needs”

So, we have discussed the approach and code at a high level, let’s see it in action!

Deploying the solution 🏗️

⚠️ Note: This will incur charges in your AWS account, and also remember to tear down the stacks afterwards.

We can deploy the global version of the solution using the following:

npm run deploy:global

and the UK specific by running:

npm run deploy:uk

When we invoke the end point we can test using the postman file found here: ./sales/postman

⚠️ Note: you can remove the stacks using the equivalent ‘npm run remove’ script.

Testing the solution 🎯

OK, let’s test the solution being deployed globally and then for the UK.

Global — Get Sales

If we call the Global version of Get Sales you will see the error response which is global:

“Sales have not been found Globally”

This is due to the following line which is built depending on locale in the get-sale.ts global lambda:

const errorMessage = stringFormat(errorMessages.notFound, "Sales");throw new Error(errorMessage);

⚠️ Note: we throw the error every time in the Lambda just to show the swapping out of messages depending on locale.

UK — Get Sales

If we deploy the UK version of the code we get a different response from the same line of code:

“Sales have not been found in UK”

This is an example of us returning the correct messages or logging based on locale being built for.

Global — Create Sale

If we now test the Global version of Create Sale with an invalid payload we will have the Global validation being used in line with the Global return message:

“Invalid: data must NOT have more than 3 properties”

This is from the following line of code in the create-sale.ts lambda:

validate(sale, schema);

Which is utilising the locale specific create-sale.schema.ts file, which has a maximum of 3 properties, and utilises the Global specific regexes:

UK — Create Sale

If we now test the UK build of the code we will see that we are using the UK specific validation and error messages when built and deployed:

“Invalid: data must NOT have fewer than 4 properties, data must have required property ‘age’”

Which is utilising the locale specific create-sale.schema.ts file, which has the following UK specific schema with its UK specific regexes:

We can use this concept to essentially override any locale specific properties and functionality that we like.

Does this approach work at enterprise scale in reality?

Yes. We used this approach on an enterprise level in one of my pervious roles that had around 15 different domains which made up the overall solution, all of which was in a monorepo.

This was then easily deployed to several other countries in quick succession, with their own locale specific requirements baked in (overridden), with an estimated saving of £300K in development costs alone.

Summary

I hope you found that useful as a high level concept on how you can build once and consume in all other countries as your business expands!

Wrapping up 👋

Please go and subscribe on my YouTube channel for similar content!

I would love to connect with you also on any of the following:

https://www.linkedin.com/in/lee-james-gilmore/
https://twitter.com/LeeJamesGilmore

If you enjoyed the posts please follow my profile Lee James Gilmore for further posts/series, and don’t forget to connect and say Hi 👋

Please also use the ‘clap’ feature at the bottom of the post if you enjoyed it! (You can clap more than once!!)

About me

Hi, I’m Lee, an AWS Community Builder, Blogger, AWS certified cloud architect and Global Serverless Architect based in the UK; currently working for City Electrical Factors (UK) & City Electric Supply (US), having worked primarily in full-stack JavaScript on AWS for the past 6 years.

I consider myself a serverless advocate with a love of all things AWS, innovation, software architecture and technology.

*** The information provided are my own personal views and I accept no responsibility on the use of the information. ***

You may also be interested in the following:

--

--

Lee James Gilmore

Lee James Gilmore

Principal Serverless Engineer | Enterprise Cloud Architect | Serverless Advocate | Mentor | Blogger | AWS x 6 Certified 🚀