Architecture decisions in Landbot

An overview of the architecture decisions we’ve made in Landbot over the years to improve the scalability and maintainability of our codebase

Jorge Arévalo
Landbot Engineering
5 min readJun 28, 2022

--

When you start building an MVP to validate an idea, sometimes there are design decisions that are questionable from the point of view of software architecture. You need to validate a hypothesis with a small user base. The scalability is not a problem (yet).

But brace yourselves, because you will need to replace your quick-and-dirty solutions with solid and well-proved implementations in the unlikely event that the product matures into the foundation of a profitable business.

We’re kind in the process of refurbishing the house, by applying the cool concepts of world-famous Clean Architecture. Let us share a few highlights about how we’re doing it.

In the beginning, everything was Django and Django Rest Framework…

Don’t misunderstand us. Django and Django Rest Framework are amazing pieces of software. Great for the purpose they were created: building REST APIs based on the MVC pattern. A common way to build MVPs and make your site live in a quick and reasonably clean way.

The Clean Architecture shows up

Once you get that, if your product grows, you will probably need to think deeper about the best way to manage the (also growing) flow of data and new business needs.

Clean architectures are one honking great idea — let’s do more of those!

At this point, common industry-standard concepts come to the table: scalability, maintainability, growth, sharding, ports and adapters, clean architectures, quitting your job, and starting a new life in the mountains… And probably, the adoption of clean architectures is one of the smarter things you can think of (quitting your job aside… Just kidding. Don’t)

Those architectures normally give your codebase these 2 features:

  • Better testability
  • Independency (from frameworks, from UI, from databases, from thirds parties…)

But it’s also common to enter into software architecture Holy Wars: which one is better and why?

Well. It depends on your specific business case. In our case, we took:

  • A few technical patterns of DDD (entities, value objects, repositories, domain events)
  • The module distinction of hexagonal architecture (application, infrastructure, domain)
  • One of the main properties of clean architectures: the dependency rule, or quoting Uncle Bob: source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle

And used them in our code base

Original image at https://miro.medium.com/max/1290/1*7wdBqL-xOCpw1d_O97_2lQ.png

Let’s see some actual code to better understand how we work with these concepts. For example, this is the summarized code structure of our campaigns app

Let’s dig deeper. First, the inner layer: the domain

Inside the domain, we have our main entity, Campaign. We use a little bit of typing and pydantic library for type validation

As you probably noticed, some of the types are basic Python primitives, like int, bool, or str, and other ones are complex types. Specifically, value objects, which define the properties of our domain (AudienceUUID, BotUUID, etc).

And, in concept, they can be as simple as this

Apart from that, we also define our campaign-specific exceptions inside of our domain module. Because we believe they also belong there.

Finally, we define the interface with the ports also here. But just the interface, not the implementation.

The implementation lives in an outer layer: the infrastructure layer. But will go with that later.

Now, let’s go with the next layer: the application one. Where use cases live

The structure of one use case couldn’t be simpler. A Python class with 3 important concepts to understand:

You can see them all in the screenshot (a simplified use case)

Last but not least, there is the infrastructure package, which would represent the Adapters layer in the Clean Architecture schema

Remember the domain layer above? It contained the interface with the ports. Now, here you have the (Django-specific) implementation

You’re probably missing the more external layer. The web one. In our case, this is just Django Rest Framework viewsets, exposing a REST API to our frontend apps. With a little bit of tunning, but in the end, the viewsets just call use cases.

For example, here is our endpoint to create a new channel, as part of a Django Rest Framework ModelViewSet subclass (don't worry about the meaning of channel, just abstract from it)

In conclusion, it may sound obvious, but we believe that, in software architecture, there’s no silver bullet. It all depends on the specific needs of your business. This cherry-picking of well-tested industry standards suits us, so far.

And for you? Let us know in the comments!

--

--

Jorge Arévalo
Landbot Engineering

Software developer and trainer. My main focus are Python language and Django framework