Backend code in python

How to build a Python backend?
Part 1: internal architecture
1/2

So you want to create a full-featured web application and you’re wondering if you should use a large framework like Django or something more minimal like Flask ? But what if you really need something in the middle? What if you want something simpler than Django because your frontend uses a technology like React or Angular ? What if you need more than just a Web API like the one you can build with Flask because your apps handles complex business logic and/or interact with other systems asynchronously?

This article describes how we’ve built this kind of backend service with the following principles in mind:

  • Easy to maintain architecture
  • Ready-for-production
  • Taking full advantage of asyncio for the API, business logic, and interaction with third-party systems

Prerequisites : have a recent Python installed (3.9) with pip should be enough

aiml-scaledev

1. Domain Driven Design

First, let’s talk about architecture!

There is a lot to learn from architecture design when you want to build real world applications. When you look at most of the code examples provided by Flask or FastAPI you get a very simple application with a REST API with only a simple handler per endpoint. In real applications you want to separate your business logic from the API calls so you can interact with the app via other canals such as GraphQL API, or RabbitMQ messages. You also need to deal with one or more storage systems, a database, a caching layer, an object storage service, a secret store, and more complex systems like cloud providers APIs, Kubernetes, etc.

Читайте также:  Html mailto with html body

To properly implement separation of concerns and abstract interactions with other systems, Domain Driven Design (DDD) concepts provide a nice toolbox to look into. The Architecture Patterns with Python Book (available online here ), is a gold mine for understanding how to implement the DDD architecture in Python. It provides tons of step by step examples for every concept so you can understand why you should or shouldn’t apply them. This is a must read, and most of what is presented here is based on this book.

So we’ll walk you through an architecture composed of 3 layers: Domain, Application, and Infrastructure. The Domain layer defines the Data structures in plain Python objects: the business objects. The Application layer holds the brain of the App: the business logic. Finally, the Infrastructure layer is the «arms and legs» of our App: the part that interacts with the external world (HTTP API, database, file system, servomotors, etc).

So let’s create our application’s skeleton with the wonderful poetry :

mkdir myapp cd myapp pip install poetry poetry init mkdir -p myapp/application mkdir myapp/domain mkdir myapp/infrastructure

You should have something like:

├── myapp │ ├── application │ ├── domain │ └── infrastructure └── pyproject.toml

1.1 Domain

The Domain layer is a model representation of the services. It really is the core of our services and it must be able to evolve fast. This layer doesn’t depends on any other layer (following the dependency inversion principle) and imports no external libraries (unless for justified exceptions, it only consists in raw python code).

A domain is a dataclass defining a business object. Most of the methods of these dataclasses consist of helpers manipulating the dataclass’ state. Some of these classes are abstract classes, implemented by other classes from the infrastructure layer.

Methods of these classes can return Domain objects, states («something went wrong», «no problem here», «only steps 1 and 3 worked»…), or nothing.

The general rule is to put as much stuff as possible there.

For example, here is an object that represents an entry in our todo app. And yes, our example will be a todo app! (as we all do ^^).

import uuid from datetime import datetime from dataclasses import dataclass, field @dataclass class TodoEntry: id: str created_at: datetime content: str tags: set[str] = field(default_factory=set) @classmethod def create_from_dict(cls, content:str) -> "TodoEntry": return cls(id=str(uuid.uuid4()), created_at=datetime.utcnow(), content=content) def set_tag(self, tag: str) -> None: self.tags.add(tag)

Did you notice that we heavily use Python types? This is really a good way to get something working quick and with confidence. We strongly advise you to use them and enforce it in the CI so you won’t have surprises at execution time.

Источник

Оцените статью