Shoffr System Architecture

Early architecture is a critical part of an early-stage, tech-enabled company. It has to enable many, often conflicting things. Agility of feature delivery, stability under rapid growth, easy evolution towards higher scalability, are all critical and the early design choices should power them all. Earlier we wrote about the tech principles we apply at Shoffr to build our systems. Today we want to showcase the high level architecture to which these technical requirement and our principles have led us.

Behold the Shoffr monolith!

Why Monolith?

Distributed systems are a pain to operate, and we would rather not deal with that complexity unless we must. Our monolith is fairly well-designed and modular (even if I say so myself), and has enabled quick iterations.

Also, Shoffr’s scale is not enough at the moment to justify a separation of service for reason of differential scale.

So a majestic monolith our system will remain for the foreseeable future.

External Touchpoints

We decided that we will build all touch-points where we want a differentiated experience. These would be ALL guest touch-points and some internal ones. The blue boxes on the left side indicate the experiences we have chosen to control – the website for our guests (and mobile apps etc in the future), the driver app for our driver to manage their work, and the internal admin portal for opearations team.

We chose to outsource some capabilities where good/affordable tools already exist and we have no need to re-invent the wheel. These are the blue boxes on the right side. These are the many external tools we use to run the business – Google maps for calculating fares, Zoho for invoicing and accounting, Gupshup for send SMSes, Slack for internal comms etc.

Communication between all these touch-points flows via our backend.

Edges of the Backend

The backend is a 3-edged system – incoming traffic, outgoing traffic, and database interaction.

Incoming traffic from all user facing properties enters via auth-protected REST layers. Each end product like website or admin UI has a custom set of end-points (to create modularity if we ever have to split them into separate pieces). So while the logical architecture above has backend-of-frontend like boxes, in reality these are just java packages in the codebase.

The second edge is were we talk to our database. We use MySQL as our datastore and do some amount of in-mem caching. Again, we did not want to complicate things by adding external caches like Redis etc and our data fits in quite comfortably.

The third edge is all the external integrations that we have. All of these integrations are routed via respective “gateway” packages so that it is easy to find what functionality of these tools is being used and how. These “gateways” also help localise any changes to these integrations.

Domain/Core Services

These services are the bottom of our abstraction pyramid. They encapsulate the core domain models and business logic/invariants pertaining to them. Each of them manages one (logical) entity and exposes APIs for other parts of the system to interact with that entity.

We decided that domain services will never know about each other. This helps us avoid the typical mess of every service calling every other service and maintains decoupling between them. Any orchestration between them is done externally.

Since integrations are essentially libraries, any service is allowed to use them, though we try to avoid it. e.g. User Service uses Zoho integrations to maintain user info in both places.

Workflows

Most high level actions (e.g. book a trip, create an account) are made up of of steps across multiple core entities. Since these entities cannot talk to each other, we use workflows to model high level actions and stitch together actions across multiple domain services.

Additionally, similar action taken from different sources may involve a slightly different set of steps. e.g. A guest cancellation triggers a different SMS than a Shoffr initiated cancellations. So we have separate workflow components for our website, our admin panel, our driver app, and so on. In cases where behaviour is same, they all converge onto the same internal implementation.

Adopting the isolated domain service orchestrated via workflows paradigm has allowed us to be extremely flexible in how our system behaves. We can trivially move around and change individual workflows to build new behaviours and features and since each workflow typical caters to a single high level action, any errors are unlikely to cascade and testing is very easy. All together, we can iterate very fast.

Technology Choices

We decided that we will use the tools which are suitable, and among all the suitable ones we will prefer the tools we are most familiar with.

The backend is built in Spring Boot, the customer website is built in Reactjs, and the driver app is built in react-native – all choices made using the above criteria.

The Admin UI is built using Thymeleaf and is bundled alongwith the backend – a somewhat unusual choice in today’s world of client side frameworks. There are two reasons for this.

We expected that we will be showing/processing lots of data on it and so wanted it to be rendered completely on server-side for efficiency. While the current version isn’t entirely server rendered, most of it is and it performs quite well – so that perspective has played out nicely so far.

The second reason was simply that I knew how to use Thymeleaf but not React. So even the first version of the website was built in Thymeleaf in the semi-SSR style. We moved to React on the website somewhat later.


So that’s where Shoffr is at the moment from a system architecture perspective. Stay tuned for more BTS info on this blog, and don’t forget to Book your Shoffr if you are flying in/out of Bangalore!

2 responses to “Shoffr System Architecture”

  1. Mo Naqvi Avatar
    Mo Naqvi

    Did you have to use any messaging framework like Kafka or RabbitMQ? Looks like you were not needing AI/ML work. I do see lot of opportunity for your app. In case you have to use ML pipelines e.g. Airrflow. Which one will you use in early stages of startup?

    Like

    1. Kislay Verma Avatar

      Currently we do not use any messaging system like Kafka since we didnt really need it. We do a fair bit of asynchronous work using multi-threading.
      Again, not a lot of ML opportunities just yet (not least because data volumes are small). I’ver heard good things about Airflow but we are still far from there.

      Like

Leave a comment

I'm Veronica

Welcome to Craftfully, my cozy corner of the internet dedicated to all things homemade and delightful. Here, I invite you to join me on a journey of creativity, craftsmanship, and all things handmade with a touch of love. Let's get crafty!

Let's connect