Does research = usability research? I don’t think so.

Posted on by Laszlo Priskin


I often find myself in discussions in which people ask “what is the role of ‘design / user research’” or “how can ‘research’ support the product development process?”. In various discussions, it also often happens, that ’design / user research’ is mentioned as the synonym of ’usability research’. You can find amazingly well-crafted ‘101 guides on how to conduct usability studies’ and more and more organisations keep using those techniques naturally.

The phenomenon which suggests that ‘design / user research’ equals ‘usability research’ made me think. In the past few years, I was lucky to take various ’research challenges’ within Skyscanner’s Apps Tribe and in its Design Team. As our product grows, we face more and more complex problems. It strikes me that we need to understand the nature of human-beings more and more profoundly.

In this journey, Steve ‘Buzz’ Pearce and Bálint Orosz, two of my professional mentors at Skyscanner, inspired me to try out or develop new methods in order to reply to those fundamental questions that our travellers are faced with. This journey helped me in realising how diverse the world of ‘design / user research’ is and also helped me in realising that besides ‘usability research’ multiple other fields of research exist and they can also add significant value to product development processes.

Let me share with you a framework which I call the ‘Four Layers of Research’. It is actually more like a research mindset and it would be great to hear whether you can relate to it and also to hear what methods you use in the case of the below-mentioned ‘layers’.

The Four Layers of Research


1. Usability research

When building products, a highly important factor is whether or not people can actually use what we build. To illustrate… if they would like to move forward in our app, will they find how to take the ‘next step’ or if they would like to ‘go back’ one step, will they figure out how to do it? In this sense, usability research is all about making sure that the way in which we realised our solution is in line with what people expect and what feels natural for them.

Simply put, in usability research studies, we are not focusing on the question of whether people need the ‘Back button’ or not, we assume that they need it. The question we focus on is if, in the moment that they would like to go back, they know immediately, intuitively and without further thinking how to do that.

2. Valuability research

This area of research is all about understanding whether people actually need a ‘Back button’ or not. Valuability research could help a lot in validating or falsifying a solution we plan to build for our travellers.

‘Validating or falsifying’ and ‘plan to build’ seem to be key terms here. We all have many nice ideas on what to build for our users, but one of the most important questions is whether people really need that solution or not. In the case of valuability research studies, we consciously ignore whether our solution is usable or not, but we focus on the point whether our solution adds value and meaning to people’s every day life or not.

In other words, does our solution really resonate with our travellers’ needs, and does it really solve something valuable for them or not. Valuability research could be a powerful tool in the ‘product discovery phase’, more specifically at that stage before we start building anything, at that stage when we’re just about planning to build ‘something’.

Honestly speaking, for me, separating ‘usability’ and ‘valuability’ questions in research studies is extremely hard. In the case of prototypes, there are so many things that create ‘noise’ and makes it hard to identify what’s the reason that our solution fails in user discussions. The ultimate question is always there – did our solution fail to work because users don’t need it or because we created something absolutely unusable for them?

To overcome this stage, emotional intelligence best practices and the deep analysis of people’s emotions and mental state helps me a lot. Can you recall memories of when a user realised the value of a feature you work on and starts talking about it honestly and passionately? Shining eyes could be a good sign that you might have built something lovable (on the other hand, I try to keep in mind Peter Schwartz’s thought: “It is always worth asking yourself: “how could I be wrong?”).

3. Contextual research

There are two different types of situations that regularly come up in our product development processes:

  • what are those needs of our users that they have not yet realised, but would really love if we figured it out for them?
  • or we come up with new directions / new products / sets of features for a group of people and we start believing that it would add lots of value for them, but how could we validate or falsify our assumptions and how could we learn more about their context and their environment in order to fit into those naturally?

In such situations, it could be best to ‘live and breathe’ with those people for whom we would like to build ‘that next big thing’. It’s often mentioned as ‘ethnographic research’ or we can call it ‘contextual research’ as well.

At this early stage of a new product or feature seed, it could easily derail us if we don’t experience directly, but instead assume we understand, how the people for whom we plan to build feel, live and behave. In the short run, it’s of course faster, cheaper, easier and more comfortable to ‘imagine’ how that group of users could feel and behave. But in the mid-run, it adds lots of value (and helps decrease risks) if we jump into the context of those users and try to understand every aspect of their life and their emotions. In product development, we always refer to the importance of ‘the users’ context’ and to the importance of their emotional and mental state. The most meaningful way for us is if we just spend time amongst our users, talking with them, living with them and, in this way, obtaining a deeper understanding of them.

Being with them also enables us to understand them consciously. This is one form of what we call research bringing ‘people’s context in the house’ and opens up opportunities for product design to come up with solutions that really resonate with people. To be very pragmatic, contextual research can help you to understand how people live and what emotions they have and you may spot a need that leads you to design a ‘Back button’ (then test its valuability and its usability aspect).

4. Conceptual research

Have you ever found that you have a very fundamental question, everybody refers to it around you, but you never had the chance to spend enough time with it, go deep enough with it and to understand how it’s embedded in human nature? We love these fundamental questions such as ‘what is trust’, ‘what is personalisation’ or ‘who our travellers are’. These help us question the status quo by going deeper and deeper day by day.

To illustrate this with a tangible example, in the case of our trust research, we turned to respected professors and subject matter experts, in the fields of social sciences and behavioural psychology. We examined various concepts, tried to embrace as many thoughts as possible about the abstract notion of ‘trust’ and thought how we could apply our learnings to the world of digital products. Then we distilled our learnings into a practical tool we called the ‘Trust Map’. The Trust Map enabled us to analyse our iOS application through the lens of trust (based on feedbacks we captured from our travellers). In the framework of a workshop, we came up with various ideas on how to move forward. Of course, we had tons of ideas, but as we had those many ideas on a sheet of paper, we started to realise how those ideas were connected with each other and we could synthesise them into topics. Now, we had a ‘set of topics’ on the table and we think that if we further explore them, they can help us build more meaningful and trustworthy relationships with our travellers in a more ‘human way’.

So how did conceptual research help us? We translated this abstract substance called ‘trust’ into opportunities in our product. And as a squad or a design working group picks up one of these topics, they can start a focused ‘product discovery process’: do some contextual research to gather some real-life experiences, then craft and prototype solutions, test whether those solutions are valuable for users, iterate on them and if they are confident about the value of their solution, then test its usability. At the end of the journey, release and learn. And iterate and learn and iterate.


László Priskin, Design / User researcher at Skyscanner. László is based in Budapest, Hungary, working as a team member on Skyscanner’s renewed mobile app available on Android & on iOS. He started sharing his thoughts, because he passionately believes in the power of discussion. He thinks whatever is written above will be outdated in a few weeks’ time, because building products means that we inspire each other, criticize each other and continuously expand our ways of thinking. László is happy to get in touch with you in the comments below, on Linkedin or Twitter as well. Views are his own.


This blogpost is part of our apps series on Codevoyagers. Check out our previous posts:

The present and future of app release at Skyscanner

Cooking up an alternative: making a ‘knocking gesture’ controlled app

How we improved the quality of app analytics data by 10x

Designing Mobile First Service APIs

How We Migrated Our Objective C Projects to Swift – Step By Step

Analytics and Data Driven Development in Apps

Transitioning From Objective C to Swift in 4 Steps – Without Rewriting The Existing Code

Sign up for email updates from the CodeVoyagers team

From Flask to aiohttp

Posted on by Manuel Miranda

From Flask to aiohttp


This post is about how to have a global context shared during the flow of a request in aiohttp. It is structured as follows:

Why, the context

In Skyscanner hotels we are developing a new service with Python 3 (h*ll yeah!), asyncio and aiohttp among other tools. As you can imagine, the company architecture is full of different micro services and tracking user journey through them can be really painful. That’s why there is a guideline telling that all services should use something that allows us to track this journey between all services. This something is the X-CorrelationID header. So, to ensure proper traceability, what our service should do is:

  1. All calls to Skyscanner services should send the X-CorrelationID header.
  2. All log traces related to a request/response cycle should contain the X-CorrelationID.
  3. Return the X-CorrelationID used in the Response.

Request/Response cycle with X-CorrelationID header

From the diagram above, you can see that we will be reusing the header in many places (calls to services, log calls, etc…). Knowing that you may realize that this header should be stored somewhere accessible from everywhere in our code.

If you’ve ever worked with aiohttp, you may have seen that there is no way of sharing state or storing global variables within the request/response cycle. The way of sharing the information of the request is by propagating the ClientRequest object throughout all the function calls. If you’ve ever worked with Django, it’s the same pattern.

Obviously, we could do that and finish the post here, but this is totally against clean code practices, maintainability, DRY and some other best practices principles. So, the question is, how can we share this variable during all the request/response cycle without passing it explicitly?

At that point, I decided to do some research, ask other engineers about similar patterns, check code from other tools/frameworks, etc… After a while my brain looked more or less like that:


So yes, I came up with an interesting pattern which is how Flask is using threads to store local information like the request object. I won’t go deep into how Flask works, but just to give an idea let’s read a paragraph and see some code extracted from “how the context works” docs section (take your time):

The method request_context() returns a new RequestContext object and uses it in combination with the with statement to bind the context. Everything that is called from the same thread from this point onwards until the end of the with statement will have access to the request globals (flask.request and others).

So, that piece of code means that, everything that gets executed inside the context manager, will have access to the request object. Awesome isn’t it? by just executing from flask import request in any section of our code, if we are inside the context manager call, it will return us the request object belonging to the current request/response cycle!

Clean and simple right? After digging into that, my thought was, can we do that with aiohttp? The answer is yes, next section describes how we have implemented a similar behavior with aiohttp (only with the header).

How, the implementation

To recap the previous section: “We want a variable to be easily accessible from any part of our code during the request/response cycle without the need to pass it explicitly to all function calls”.

Python coroutines are executed within the asyncio loop. This loop is the one in charge of picking Futures, Tasks, etc and executing them. Every time you use an asyncio.ensure_future, await and other asynchronous calls, the code is executed within a Task instance which is scheduled inside the loop. You can think about Tasks as small units to be processed sequentially by the loop.

This gives us an object where we can store this shared data throughout the cycle. Here some things to keep in mind:

  • aiohttp request/response cycle is executed within a single Task.
  • Every time a coroutine is called with the await or yield from syntax, the code is executed in the same Task.
  • Other calls like asyncio.ensure_future, asyncio.call_soon, etc… create a new Task instance. If we want to share the context, we will have to do something there.

Seems we are onto something right? The object we want to work with is Task. After checking its API reference you can see there isn’t a structure, function call or anything that allows us to store context information but, since we are in python, we can just do task.context = {“X-CorrelationID”: “1234”}.

Integrating task context with aiohttp

If you’ve read the previous section, you know that we want to store the “X-CorrelationID” header to be easily accessible during all the request/response cycle to be able to use it during log calls, external services calls and return it in the response. To do that, we’ve coded this simple middleware:

Note the import context line. The module is just a proxy to the Task.context attribute of the current Task being executed:

Easy peasy right? by just calling context.get(key) we will get the value stored in Task.context[key] where Task is the current one being executed. By just calling context.set we will set the value for the given key.

Note that, from now on you will be able to do a context.get(“X-CorrelationID”) from ANY part of your code and it will return the needed value if existed. This for example, allows us to inject the X-CorrelationID in our logs automatically using a custom logging.Filter:

Same pattern used for injecting the header when needed to call an internal service from Skyscanner:

For simple flows which cover most of the use cases, this works so far so good!

The ensure_future & co

As previously commented, the ensure_future call returns a new Task. This means that the custom context attribute we were using before is lost during the call. For our code, we solve this by creating a new context.ensure_future call that wraps the original one:

This part is the one I’m less happy about because it’s not transparent to the user. In future versions this will be improved.

What(,) now?

I’ve moved this simple code to a github repository and called it aiotask_context. Right now it only includes the functions for getting and setting variables in the current task. Some future work I’m planning:

  • Implementing a mechanism to propagate the context when using asyncio.ensure_future, asyncio.call_soon and similar calls. Candidates are wrapping (meh) or monkey patching (uhm…).
  • Add more control to know if current code is being executed under a Task environment or not and act accordingly.
  • Include examples in the repository like the aiohttp middleware, request passing, log fields injection, etc…

Just to finish, if you have any questions or feedback, don’t hesitate to comment and ask!


Interesting links I used:

Other projects implementing this pattern (haven’t tried them):

Sign up for email updates from the CodeVoyagers team

The present and future of app release at Skyscanner

Posted on by Tamas Chrenoczy-Nagy

Since we’ve just released our 3-in-1 update for the Skyscanner app, it’s a great time to share some info with you about how we release iOS and Android apps at Skyscanner.  In this post we would like to give you a brief overview of our app release process, how we adopted the de facto industry standards and extended them to our needs. Beside this we also would like to give you a couple of ideas where we are planning to improve in the future.

We’ll cover:

  • Separating code drops from feature releases
  • Release trains
  • Feature flags
  • Faster release cycles
  • Future ideas and improvements

Decoupling releasing features from releasing the binary

Imagine a company where multiple teams are working on the same app. The teams own different functions, or different screens of the app. They would like to work and deliver new features independently. So if team A has some kind of delay in releasing a new feature, it shouldn’t block team B to release their own feature on time. The company should be able to release a new version of the app even if not all new features are ready for it.

It is also very important to be confident that the new feature is delivering value to our users. Firstly we release only to a small number of users, and depending on their reaction we continue the rollout to all.

To achieve these goals we had to decouple releasing a feature from releasing the app binary. We put the new features behind a feature flag and only turn them on if the feature is ready to be released in production. Meanwhile we release the new binaries in a fixed schedule, so teams don’t need to synchronize their feature delivery, the schedule is available and they can plan ahead easily.

Release train – shipping the new binary on schedule

Releasing the binary follows a fixed schedule, let’s call it a release train. So if you finished your new feature on time, you can release it with the upcoming release train. If you are not ready on time and you missed the train and then you will be able to release the feature with a next train.

On both iOS and Android we have a 2-week release cycle, which mean we ship a new binary bi-weekly. The releases on the two platforms are held on alternate weeks, so each week either a new iOS or Android release train starts.

Feature flags – the tool for actually releasing features

By using feature flags, we are able to specify which features the users should see when they run the application. The feature flags can be modified remotely, so we are able to turn on and off features without releasing a new binary.

Feature flags provide a lot of value at four different stages of development:

  1. Feature is under development: If the feature is still under development and it is not ready to be released to any users, the flag is only turned on for developers – so they can iterate on the feature and commit changes without causing any user disruption.
  2. Rolling out a new feature: If the feature is ready to be released, first we usually turn it on for only a small amount of users (for instance 1%), so we can measure the reaction of the users and the quality of the feature (by checking analytics data – key business metrics, errors, crashes). If everything seems to be ok, we increase the rollout, sometimes in multiple steps, for instance 10%, 100%.
  3. A/B testing: This scenario is similar to point 2 but with variations of what we release. We would like to experiment with a new feature before releasing it to all of our users – so sometimes we can ship multiple variants of the feature and measure which one performs the best.
  4. Kill switch: If we have a feature which has already gone live and something goes wrong (i.e. the app starts crashing because of it), we can turn it off remotely.

To release the 3-in-1 update we also used feature flags. Early versions of the feature have been presented in the app binary since last December. At its early stages it was only turned on only in our development builds, then in our test builds. After we finished the development we released to 1%, 5%, 20%, 50% and finally to 100% of our users using the feature flags. Meanwhile we continuously monitored the performance of the feature. After the 100% rollout we also kept the feature flag as a kill switch for a while, so if something really bad had happened we could have still rolled it back.

Releasing a new binary frequently

Increasing the frequency of releases is a key part of keeping the pace in delivery. Frequent releases help us to experiment and iterate on our features faster. We can get valuable data about user behaviour faster so we can react on them faster. As of now we have a 2-week release cycle on both android and iOS and we are planning to further increase the frequency.

Release flow

Each Wednesday morning we have a code freeze on either iOS or Android. After the code freeze we start a ~1 week long stability period. During this period our main goal is to get confident in the stability of our new release, so we are continuously testing the app and working on fixing critical/major bugs.

After we tested and fixed all critical bugs in the new release, we still cannot be confident that the app will work well for all of our users. There can be some issues which can be detected only in production. That’s the reason why on Android at least, we use the staged rollout feature of Google Play. During staged rollout the new release is rolled out to the users gradually (for only 1% first, then 10%, 100%). We are continuously checking key business metrics (like search rate, conversion rate), and also error rate, crash rate, etc. In case the metrics look good we increase the rollout, but in case we detect any major issue, we try to disable the feature using feature flags and ship an update with the next train. If it isn’t possible we release a hotfix to fix the problem. The staged rollout usually takes about one week.

After we validated the new version in production as well (using staged rollout) we release the app globally. But the story doesn’t end here. We keep monitoring the app metrics, user feedback/reviews, crashes and react if necessary.

What’s next? – There is a lot what we can improve

Shipping a new app version to our users in every two weeks is a good thing. It is good because we can quickly iterate and release new fetaures. But is it good enough?  Let’s do some maths on how long it takes for one new feature to get to the users.

Let’s say development of this feature takes two weeks. If you sum up the length of the release process , you can see it takes an extra two weeks to get that feature shipped to our users.  Beside this – if the feature isn’t finished on time and it misses the release train, that adds an extra 2 weeks again.

So shipping a new feature can take from 4 to 6 weeks to get to 100% rollout. If we need to iterate multiple times on the feature and make several experiments or adjustments, it is even longer. This is way too much compared to a web environment with a continuous delivery flow, where you can release the feature almost immediately. Can we achieve the same for apps? We believe so, in time.

Beside the development areas in our processes, we are facing several constraints coming from the nature of apps. One of these was the review process in App Store, which in the past took at least one full week.  Fortunately Apple has worked hard to bring this down – as little as 1-2 days now, so it should not act as a blocker.

Another constraint is that apps are shipped to the users in big packages and app updates are still a big thing – and even more so if we ever had to perform a rollback of something that couldn’t quickly be hidden with feature flags.

However we can  see some promising improvements in the industry which might result in making app releases a less heavy thing. Google’s Instant Apps feature is the best example and worth checking out.

So applying full continuous delivery for apps is not something which is possible for apps at this moment in time.  But we can still apply best practices from web  and use them to increase our release frequency to get that bit closer.

Hopefully the industry will also move in a direction which supports our goals, so if our processes and tooling are sound, we should be able to capitalise on any changes without major lifting.

Tamas Chrenoczy-Nagy is a product engineer working on improving release and development processes, tools for apps.

This blogpost is part of our apps series on Codevoyagers. Check out our previous posts:

The present and future of app release at Skyscanner

Cooking up an alternative: making a ‘knocking gesture’ controlled app

How we improved the quality of app analytics data by 10x

Designing Mobile First Service APIs

How We Migrated Our Objective C Projects to Swift – Step By Step

Analytics and Data Driven Development in Apps

Transitioning From Objective C to Swift in 4 Steps – Without Rewriting The Existing Code

Sign up for email updates from the CodeVoyagers team

Not “all the technologies”

Posted on by Alistair Hann

A question I am often asked about Skyscanner is what technologies we use. It’s the kind of question that I have asked of other businesses in the past – to validate my own choices, as much as anything else. My, slightly flippant, answer is “All the Technologies”, but we are moving away from that position.

Back in 2010, Skyscanner was largely a .net shop – a bunch of, lots of SQL server and one Python service. In the following years, three changes led to a Cambrian explosion in the range of technologies we use:

  • We acquired businesses with different tech stacks to us – PHP, Postgres, MySQL, etc.
  • We made a strategic choice that we would no longer build services on .net and ultimately move to entirely using Free and Open Source Software (FOSS)
  • We switched to a model with a high level of autonomy for teams
Just some of the technologies we use at Skyscanner
Some of the technologies we use at Skyscanner


The case against diversity

The last point in that list is a tricky one – at Skyscanner we have a model that borrows ideas from Spotify’s Squads and Tribes model. The idea is a collection of autonomous start-up like teams, each with complete ownership of one or more services, able to independently deploy those services, and setting its own roadmap and goals. The model of a collection of autonomous teams is powerful because the teams can execute unencumbered, independently shipping code and delivering value to customers.

A challenge occurs when there is a feature that cannot be shipped without changes to services owned by a different team. Clever shaping of teams and feature teams can help reduce this, but there will always be some feature that requires changes outside of the originating team’s services. One way of handling that situation is that the first team takes a dependency on another team building what they want them to. Unfortunately that breaks the idea autonomous teams delivering value to customers at their own heartbeat. The first team is now delayed by the second, and the second now needs to implement a feature that

may not have been in their roadmap, so they also lose their independence. Another way of handling that is the first team makes a change to the second team’s codebase, they make a series of pull requests and deliver the feature independently. That works well if there is an efficient internal open source model. If teams are all using the same technologies and tooling, that model is a lot more efficient.

When you move to a micro-service architecture with lots of independent services, there is a risk of solving the same problems many times. At Skyscanner we invest heavily in producing tooling to avoid these situations – so engineers can focus on writing new, valuable software rather than solving the same problems that everyone else has solved. Building and maintaining that tooling is difficult when there are dozens of platforms to support. Similarly, our event logging platform team may want to build SDKs to speed up adoption, and ideally they wouldn’t have to write six.

Finally, at Skyscanner we want people to have a variety of challenges. We encourage engineers to rotate between teams and take opportunities to work on different services, and as our products evolve we need to mould our organization to the oncoming work. It is a lot more efficient to move between teams if they are using a familiar tech stack and tooling.

Thus there are many savings to be made if we narrow the number of technologies being used. That doesn’t mean only having one technology stack – there are cases where it is advantageous to have a dynamic language for rapid scripting, or high performance from an interpreted language. For reference, outside of native mobile app development, our default platforms are now Java, Python, Node and React. The reason for Node are the advantages of more rapid development when there is a language consistency between client side and server side.

How do we get there?

In terms of how we get to that position, the stance we have always taken has been not to rewrite systems for the sake of it. There is no customer value in making a change like that. We are setting a direction though – all new services should use the ‘default’ technology set. Then whenever we change things or break services into smaller components, we err on using the default technology set where it means little incremental work.

One way to encourage the shift is through the free tooling teams get for embracing the standard tools. There is a very compelling reason to use what is standard. We are also part way through migrating from co-location to AWS and again we default to using the AWS native services wherever possible, which increases convergence as well as speeding up delivery.

We are not alone in this approach. At Google there are a limited number of languages that are supported for use in production (C++, Go, Java and Python) and something like Ruby is not supported. The practical implementation of that is a list of all the things that need to be available for a language in product (HTTP server, bindings to talk to production infrastructure etc.).

What about that autonomy thing?

The key thing about the model of distributed agile teams is that it is aligned autonomy. The teams are independent to execute, but they share the same purpose and goal – all our teams are working in travel, none are working in selling pet food (for example). That alignment has to happen for technology as well.

Getting the Benefits

We can already see benefits of narrowing our technology set. We are building much richer tooling for our engineers – I was speaking to an engineer earlier today and he was saying how he and two other engineers had created a new micro service from scratch and got it up and running in multi-region AWS serving production traffic in 45 minutes. One enabler of that was ‘Slingshot’ our zero-click-to-production deployment system – every commit is shipped to production, with automated blue/green deployment and rollback. Another was our micro-service shell support for Java that provides the basic event logging, operational monitoring, etc. in order that engineers only need to write new code that is unique to their service. There is a lot more we want to do with the shell, slingshot, and other tools. We can develop that tooling more quickly if we are only doing so for a limited number of platforms.

Getting the complete benefit will take more time – it will be years before we only run on the supported technology stack. That means there will continue to be pain when making changes to some other teams’ codebases that are not in the supported stack, but that pain will be constantly reducing as we converge on a more consistent platform.

Sign up for email updates from the CodeVoyagers team

Podcast: How to build a billion-dollar software company

Posted on by David Low

From our friends at the Skyscanner Travel Podcast, hosts Sam, James and Hugh talk in fascinating detail with our CEO Gareth Williams about how solving the problem of booking future journeys, created a whole new one.

Everything is on the table – from the original days dreaming of Daft Punk, how personalities and goals evolved over time, and how the important thing is to enjoy that journey…

Sign up for email updates from the CodeVoyagers team

Video: how code is changing our lives

Posted on by David Low

Many people quote the well-worn phrase, that ‘software is eating the world’.

But have you ever stopped to think about the impact of product engineering, or put more simply ‘code’, and how it affects our everyday lives?

With modern smartphones carrying over 600 times the computing power of a good desktop PC from barely 20 years ago – combined with the fact almost everyone will be carrying one – the power of code and computing to change our world has never been greater.

In this video taken at Tech Talent Week in London, Our SVP Engineering, Bryan Dove talks about the impact of code on society and particularly our own world of travel – and how we as product engineers can really make an impact on how we live.

Sign up for email updates from the CodeVoyagers team

Backing up an Amazon Web Services DynamoDB

Posted on by Annette Wilson

At Skyscanner we make use of many of Amazon’s Web Services (AWS). I work in the Travel Content Platform Squad, who are responsible for making sure that re-usable content, like photographs of travel destinations, descriptive snippets of text and whole articles can be used throughout Skyscanner. That might be on the Skyscanner website, in the mobile app, in our newsletter or in whatever cool ideas our colleagues might come up with. Recently we’ve been evaluating Amazon’s DynamoDB to see if it might be appropriate as the primary means of storing data for a new service. If we use DynamoDB as a primary store, and not just a cache for something else, we’ll need to keep backups. But it wasn’t clear how best to take these.

After investigating the options and trying them out I wrote a summary for my colleagues in an internal blog. This is a lightly adapted version of that summary. I’ll warn you now, it’s quite long!

Continue reading Backing up an Amazon Web Services DynamoDB

Sign up for email updates from the CodeVoyagers team

Cooking up an alternative: making a ‘knocking gesture’ controlled app

Posted on by Zoltan Kolozsvari

Have you ever seen a seismometer-application running on your smartphone? It’s incredible. The refinement and responsiveness means that it picks up on even the smallest vibrations. So, what if you could use these vibrations in the way we use touch screens: to control your phone?

This was the question posed by a small team in our Budapest office, who launched ‘Project Woodpecker’ to create a knock (vibration) recognition component embedded within an application. Our aim was to see how viable and useful ‘knocking gestures’ could really be.


Continue reading Cooking up an alternative: making a ‘knocking gesture’ controlled app

Sign up for email updates from the CodeVoyagers team

The Case for Experimentation

Posted on by Colin McFarland

If you’re Picasso, don’t A/B test, but for the rest of us it’s humbling to evaluate our ideas…

Continue reading The Case for Experimentation

Sign up for email updates from the CodeVoyagers team

Atomic-Based Code Design

Posted on by Richard Greene

Atomic design is a pattern pioneered by Brad Frost that advocates building up the visual elements of a webpage from small components that combine to make ever more useful and complex forms. We at Skyscanner have also found it to be a useful analogy for how small components of code functionality can be used to build up functional, integrated web applications. Here’s our experience of using atomic-based code design.


Describing and designing code in this way makes it easier for us to abstract out and re-use shared functionality, improve testability of code and identify dependencies so that we can build the cleanest and most maintainable code possible.


Atomic Design Terminology

  1. Atoms
  2. Molecules
  3. Organisms
  4. Templates
  5. Pages

In the design paradigm, atoms are the smallest possible unit of functionality, molecules are simple features built up from one or more atoms and organisms are full features made up from molecules and presented to the user.  These could be a button, date-picker for a return trip, or a full search control with date, location and filters, respectively.  Organisms are placed into templates, which map out where they appear in relation to each other, forming specific pages. From a developer point of view, we can map this onto the underlying code that makes these function and also on to pieces of application logic that have no component directly visible to the user.

Starting Point

In our Squad (Inspiration and Discovery) we inherited an existing codebase and went about expanding and updating it. As we went along we realised that we wanted to try breaking down the existing functionality into separate re-usable components.  Part of the motivation for this was so that it would be easier to swap in and out different versions of components when we wanted to experiment with new and improved features. Another motivation was to take areas of functionality that were very similar in different places in the codebase and creat shared, reusable components for them so that they would have a single point for maintenance and testing. That way we could avoid bugs that were found and fixed in one area cropping up again or slipping by in similar areas that hadn’t picked up the original fix.

Our existing code was written in Coffeescript, bundled as Javascript to publish on the front end.  Each page was its own application and had its own styles for that page stored in a single stylesheet for the page. The code was split into logical classes, with some shared utilities, but was often quite tightly coupled with the other classes on the page and had a lot of implicit knowledge about the rest of the application. For instance, the Coffeescript classes were all defined and then placed into a namespace in the global scope. New class instance would be called wherever they were needed with the assumption that the class definition would be present in the page scope. Similarly, data returned from calls to the server was placed into namespaced global variables and processing code would expect to find the relevant variables in the global scope when invoked.


First Steps

Out first major change was to move to using Browserify to handle the building of our deployable javascript. This meant moving to defining Coffeescript classes as modules that were required wherever they were used. As such, we could see explicitly where code was being used and re-used.  We also placed our data into specific data models and could then scope access to them to only those classes that explicitly required them. At this point the code was still quite tightly coupled between classes but we could start to see where the dependencies were.

The first step was to pull out some simple, shared functionality, like our logging and server communication code. These were our first molecules, a few very basic pieces of functionality that we would re-use again and again across the code. They were composed of no more than a few functions (atoms) and gave us single points where we could add new functionality. For the logger, this gave us an easy way to add in logging to new channels, such as adding logging for Google Analytics or Facebook where it had been lacking before. For the server communication this would later allow us to experiment with substituting in more up-to-date data from local caches, on top of data from server storage, without the consuming applications ever having to know the substitution was being made.

Win Some, Lose Some

Our next step was to move across from Browserify to Webpack.  Although similar in functionality, Webpack had some additional out of the box features that were useful to us.  The ability to require CSS as well as functional code was very useful, and it also came with ability to inline SVGs as part of the build process rather than serving them up separately. With this done, we now had each of our main product pages effectively running as single large organism on their own pages.

Our first effort in putting together multiple separate organisms on a single page was with one of our newer pages, the map. We introduced a new component to give people a richer way to choose their specific flight dates after choosing their location from the map, a panel that let them see the pairs of dates they could fly out and back on. The two organisms used a shared bus to communicate with each other, a molecule that we added in at the page level. This allowed the panel to update its display based on changes to the selected location, messaged out from the map via the shared bus. We were able to add in a whole new component, displaying rich data to the user, with very little disturbance to the map code. The two organisms relied on shared expectations of data at the page level, communicating via the bus, but were otherwise ignorant of each other’s existence.

Our problem came when we tried to use the new panel component in place of the existing code on of our older pages, to see if it could duplicate the success it had shown being used with the map. We quickly found that to be used successfully in the older page, it had to be customised in a number of ways that differed from how we wanted to use it on the map. It needed to be embedded in some additional UI, which we were able to do by adding another simple molecule of functionality, adding some filters and visual padding. However, we also found that we needed to alter the layout, the styling and add in some additional small pieces of functionality that weren’t necessary when using it alongside the map.

The lesson we learned from this was that although organisms were discrete from each other they were often too dependent on their context in a given page to be fully reusable. A better way of looking at them was as a collection of molecules of functionality. The existing panel was a particular configuration of molecules, and one we could use as a template for building another organism in a new context, but was unlikely something we would re-use as a whole piece.

Onwards and Upwards

Our current work is now focusing on the styles associated with our web components and on applying atomic design principles more thoroughly across our existing code. Using Webpack’s ability to require styles like other library code we can create separate SASS files for each component and then combine them as needed. Instead of a single master style file, we build up and extract our overall stylesheet for any given page from the molecules we have pulled together to build it. To avoid problems with CSS ordering and specificity we are looking at adopting CSS Modules. With Webpack we can generate unique, locally scoped class names every time we consume styles for a molecule. The small amount of duplication this may cause is more than made up for by the ease of mixing and matching of different style components for our purposes.

For our existing code we are steadily decomposing the existing page-level applications into less tightly coupled molecules. In some cases, this is only a small change to the existing classes, in others it requires pulling out a lot of the implicit knowledge they have about the state of the rest of the page and making them require or access it explicitly. For one of our search controls, for instance, this allows us to pull out certain search fields to re-use in a different and more specific selection UI when the user has begun to narrow their search for destinations on a page. We can see clearly what components needs to function so that we can re-use them easily elsewhere. We can also see what depends on them and how they are connected to the other molecules around them for when we want to try putting new or updated molecule in their current place to test out new designs.

The more we break down our code into these building blocks, the quicker it is when we drop in new UI or UX on the page, when we update our data sources and when we want to try out or discard new pieces of functionality. If we want to try out a new service, we are better placed to put together a working prototype from the molecules we already have lying around, as our applications are designed to be broken down into pieces and rebuilt as needed.

Richard Greene is a Software Engineer working in Skyscanner’s Inspiration and Discovery Squad.

Sign up for email updates from the CodeVoyagers team