Under The Hood of Effectiv
March 13, 2018
After announcing Effectiv last month, I received quite a number of questions regarding the technical stack behind the platform. Therefore, in this blog post, I will tell a bit more about the underlying architecture and explain some of the technical decisions that I made along the way.-
The good thing about side-projects is that they are a perfect opportunity to experiment with new technologies and libraries. Applying these new learnings on a real-world example makes it a lot more interesting and I also find it the best way to fully grasp the ins and out.
As you will read, I have been playing around with several new frameworks during the development of Effectiv. Obviously, this has not always been the most efficient way to deliver, but I tried to focus as much as possible while also enjoying myself in the process.
Now, let’s focus on the internals:
The frontend of Effectiv is built entirely in React. My choice for React was quite evident. Not only do I absolutely like the component-based approach it enforces, but I also found the active community and vibrant eco-system a huge plus.
Before Effectiv, I already worked on some React(-Native) projects, so I was quite familiar with many of its best-practices.
For the first time, however, I decided to use Flow.js for adding some level of type safety to my Javascript code. The learning curve initially slowed me down, but after some time I couldn’t live without. In general, I would say most of my components have at least 85% coverage, and the missing 15% is mostly due to the use of external libraries that have no Flow definitions.
For the styling of components, I decided to go with JS-in-CSS instead of regular CSS. Placing both component’s logic and styling together in one file felt very natural to me. Also, the fact that styling code is just plain Javascript implied a whole lot of advantages like the ability to create helper functions and linter support.
When I started with Effectiv last summer, I did quite some research about the best styling framework and eventually decided to use Aphrodite. I particularly liked its functional syntax, its good performance and the fact that it only injects the exact styles needed for the render into the DOM.
To make components more reusable and customizable in terms of styling, I created a small open-source library called aphrodite-helpers. The library provides some helper functions to easily inject Aphrodite stylesheets into components and merge them with the components’ own styles.
Today, however, the Aphrodite community feels a bit abandoned and more advanced styling frameworks have emerged like Styled-Components, Emotion, and JSS; At some point in the future, I will probably consider to start using one of them, but for now Aphrodite does the job perfectly.
All API interactions are driven by GraphQL and handled by the Apollo framework on both client and server. My choice for GraphQL over REST was quite implicit since all projects that I worked on during the last year were GraphQL-based, and I didn’t miss REST for a bit.
At the client side, before using Apollo, I first tried Relay Modern to handle the GraphQL operations. Relay allows components to specify what data they need, making them less independent of the data needs of their inner child components. A compiler builds all queries ahead of time and generates code to process the responses, boosting performance significantly.
To create a more smooth and responsive user experience, Relay Modern provides a feature, which I was very eager on using, called optimistic updates. An optimistic update means that when the user submits a form, the UI behaves as though the form was completed before receiving its response from the server.
Unfortunately, I struggled a lot to get optimistic updates working with Relay Modern. At that time, both the documentation and debugging options were minimal, and I just couldn’t get my head around how the data was managed by their local data store. Also, the more complicated my view hierarchy became, the more difficult it was to stitch together all GraphQL fragments.
Therefore, I decided to replace Relay Modern with Apollo to handle all GraphQL operations. Obviously, this implied a huge refactor, but because the Apollo ecosystem and community are a lot more mature, I knew it would pay off in the long run. Apollo also supports optimistic updates, and with their proper documentation and debugging tools, I got it working in no time.
In the beginning, when I created this React project, I also added Redux for state management. However, since Apollo has its own data store to manage all data retrieved from the API, Redux became quite redundant as it was holding only a handful of boolean settings.
Luckily, at the end of 2017, Apollo introduced apollo-link-state which enables to encapsulate all of the application’s state inside the Apollo store and maintain a single source of truth. This helped me to abandon Redux and consistently manage all data through Apollo.
On the server side, things have been exciting too.
Instead of implementing a typical container-based architecture, I decided to try out a serverless architecture. Not only was I interested in the learnings on how this could work, but also the pay-as-you-go model and the continuous scaling are two huge benefits that are perfect for a side-project like Effectiv.
-
My cloud vendor of choice is Amazon AWS because they offer the most functionality for serverless functions with AWS Lambda. All data is stored in a Postgres database which also runs on Amazon AWS.
My main Lambda function is implemented in Javascript and serves as the GraphQL endpoint that the web app interacts with. Some smaller Lambda functions, which are triggered as a cron job, are implemented in Go.
To efficiently develop and test these functions, and to deploy them consistently, I make use of the Serverless Framework. This framework provides a toolkit for deploying and operating serverless architectures and is entirely provider agnostic. They have excellent documentation and plenty of examples, making it super simple to manage serverless functions.
Also, the frontend React bundle is deployed using the Serverless Framework. In the same manner as I deploy my serverless functions, I can upload all frontend assets to an Amazon S3 bucket. From there, the files are propagated to AWS CloudFront, which is a global content delivery network (CDN). Using a CDN ensures that the Effectiv web app can be loaded with low latency across the globe.
Finally, user authentication is entirely handled by AWS Cognito. Not having to implement all logic behind a secure and scalable user authentication system saved me a lot of time that I instead could use on building the core of the application.
This pretty much covers the most relevant components behind Effectiv as it is today. Generally, I am pretty pleased with the quality of my code and the way things are. Having a stable core now gives me the perfect opportunity to experiment with more advanced technologies and to build something truly meaningful.
If you want to follow my progress along the way or be notified of upcoming releases, sign up here or follow @effectiv_app on Twitter.