... | ... | @@ -18,7 +18,7 @@ You can see here a picture of Margaret Hamilton standing next the code she and h |
|
|
|
|
|
The next evolution of software engineering was to be able to reference other code files, modules or libraries from the main file, allowing you to split your code, try to use the separation of concerns principle and group code in different files, each file dedicated to a specific concern. We still use this principle in Python today. Whenever we write an `import` statement to make some code available, whether that code was written by us or by someone else, whether is only being used for this one program or if it was intended to be re-usable.
|
|
|
|
|
|
Jump forward a few years and re-usable code has now become a thing. One of the techniques that made re-usability a success is, in my opinion, Object-Oriented-Programming (or OOP for short). A technique at which Python was one of the first languages to excel at. Having to write class definitions encouraging developers to then use them more than ones, having them think about inheritance and grouping common features in parent classes.
|
|
|
Jump forward a few years and re-usable code has now become a thing. One of the techniques that made re-usability a success is, in my opinion, Object-Oriented-Programming (or OOP for short). A technique at which Python was one of the first languages to excel at. Having to write class definitions encouraging developers to then use them more than once, having them think about inheritance and grouping common features in parent classes.
|
|
|
|
|
|
But, at this point, programs still exist as a single unit, each of them spread among multiple files, using several libraries but still running as a single unit, designed to accomplish one main goal, every task required to accomplish this goal being performed by that single program or project unit. This is what we reference as a **monolith**.
|
|
|
|
... | ... | @@ -65,7 +65,7 @@ Django advertises itself as coming with "batteries included". By that meaning th |
|
|
|
|
|
The fact Django comes with a lot of functionalities doesn't mean you do have to be using all of those features to take advantage of the framework itself.
|
|
|
|
|
|
On the other hand we have minimalist frameworks that come with very few features other than routing. With this type of framework, if you ever need to perform a common action such as connecting to a database or providing authentication, you will have to either write those functionalities or resort to some third party library. The biggest risk here being for developers to decide on using homemade code for solving problems they might have underestimated, one of the few things that come to mind right away are authentication or date management.
|
|
|
On the other hand we have minimalist frameworks that come with very few features other than routing. With this type of framework, if you ever need to perform a common action such as connecting to a database or providing authentication, you will have to either write those functionalities or resort to some third party library. The biggest risk here being for developers to decide on using home made code for solving problems they might have underestimated, one of the few things that come to mind right away are authentication or date management.
|
|
|
|
|
|
Even if it's not perfect, I'll try to go with a car analogy here.
|
|
|
If you were to fix a car, would you rather have access to a fully-equiped garage (at no extra cost) even though you know you won't be using half of the tools available in said garage or would you rather do that in the parking-lot in front of your house, knowing that you have very few tools at your disposal but that there is a DIY store nearby where you will be able to find most tools you'll need and that are not included in the service kit you received with your car?
|
... | ... | @@ -85,3 +85,182 @@ As a last resort, Django having its own ORM (Object Relational Mapper), I wonder |
|
|
In summary, if someone wants to make the claim that "Django is too bloated for micro-services", I would like to know what exactly they mean by that and if they have evidence to support that claim. Until that happens, I will claim that "Django **is not** too bloated for micro-services"
|
|
|
|
|
|
## Storing extra batteries safely (configuring Django for micro-services)
|
|
|
|
|
|
As mentioned above, chances are that when writing a single micro-service, you are not gonna use all the features of Django (although you might still use most of them across multiple micro-services for the same project). One of the first things to do is therefore to look at Django's `settings.py` and remove whatever is not going to be needed.
|
|
|
|
|
|
The first thing we are going to look at is the default `INSTALLED_APPS`:
|
|
|
|
|
|
### `INSTALLED_APPS`
|
|
|
|
|
|
`settings.py`
|
|
|
```
|
|
|
...
|
|
|
INSTALLED_APPS = [
|
|
|
'django.contrib.admin',
|
|
|
'django.contrib.auth',
|
|
|
'django.contrib.contenttypes',
|
|
|
'django.contrib.sessions',
|
|
|
'django.contrib.messages',
|
|
|
'django.contrib.staticfiles',
|
|
|
]
|
|
|
...
|
|
|
```
|
|
|
|
|
|
`'django.contib.admin',` is only going to be useful (probably only during development) if you are going to be connecting to a database. A micro-service that does not connect to any Django-supported data backend will not need this application. A micro-service that would provide the time of day for example will not need that.
|
|
|
|
|
|
`'django.contrib.auth',` will be useful if the micro-service you are writing is in charge or keeps track of authentication. Authentication is mostly useful for micro-services exposed to the public. Some microservices are not, they are only called from other micro-service. Be mindful that the defaults for `django.contrib.auth` are to use a database backend. In a significant portion of cases, this config line can be omitted as well.
|
|
|
|
|
|
`'django.contrib.contenttypes',` has 2 main uses; it allows for things like `GenericForeignKey` as well as creating default admin permissions for different models. If you are not going to be using any of those 2 features (which is very likely with micro-services), you can also remove this application.
|
|
|
|
|
|
`'django.contrib.sessions',` is used to keep a "session" which is a permanent state between requests. Most micro-services are stateless and you will therefore most likely not need it. You might want to keep it for `DEBUG=True` though if your micro-service uses Django REST Framework and requires authentication and uses session authentication or if you use the admin. If you don't know what Django REST Framework is, don't worry about it right now, we will cover it extensively later in this book.
|
|
|
|
|
|
`'django.contrib.messages',` is the application responsible for displaying success/failure messages and other informations on web pages and inside the admin. If you use the admin in `DBUG=True`, you will have to keep it, otherwise you can remove it.
|
|
|
|
|
|
Finally `'django.contrib.staticfiles',` is used by both the admin and Django REST Framework's browsable interface. You will only most likely use it only if `DEBUG=True`.
|
|
|
|
|
|
A typical `INSTALLED_APPS` for a micro-service might look like this:
|
|
|
|
|
|
`settings.py`
|
|
|
```
|
|
|
...
|
|
|
INSTALLED_APPS = [
|
|
|
'my_application',
|
|
|
]
|
|
|
|
|
|
if DEBUG is True:
|
|
|
INSTALLED_APPS += [
|
|
|
'django.contrib.sessions',
|
|
|
'django.contrib.staticfiles',
|
|
|
]
|
|
|
...
|
|
|
```
|
|
|
|
|
|
### `MIDDLEWARE`
|
|
|
|
|
|
Having pruned non-useful application, the next item to look at is `MIDDLEWARE`. In the default `settings.py`, `MIDDLEWARE` looks like this:
|
|
|
|
|
|
`settings.py`
|
|
|
```
|
|
|
MIDDLEWARE = [
|
|
|
'django.middleware.security.SecurityMiddleware',
|
|
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
|
'django.middleware.common.CommonMiddleware',
|
|
|
'django.middleware.csrf.CsrfViewMiddleware',
|
|
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
|
'django.contrib.messages.middleware.MessageMiddleware',
|
|
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
|
]
|
|
|
```
|
|
|
|
|
|
`'django.middleware.common.CommonMiddleware',` is the only truly required line in this config as it is central to Django's internals.
|
|
|
|
|
|
`'django.middleware.security.SecurityMiddleware',` and `'django.middleware.clickjacking.XFrameOptionsMiddleware',` are strongly recommended if your micro-service is going to be accessible publicly but are not needed if that particular micro-service is going to be only accessible by other micro-services.
|
|
|
|
|
|
`'django.contrib.sessions.middleware.SessionMiddleware',` is the middleware associated with `'django.contrib.sessions',`, so you'll only need it if `'django.contrib.sessions',` is part of your `INSTALLED_APPS`.
|
|
|
|
|
|
`'django.middleware.csrf.CsrfViewMiddleware',` is useful if your micro-service is going to be accessed by web pages created by that same micro-service. Most micro-services are only serving API's so it is very unlikely. You will need it though if you are using the admin. This middleware is not needed if you use Django REST framework as it have its own implementation of CSRF.
|
|
|
|
|
|
`'django.contrib.auth.middleware.AuthenticationMiddleware',` is not going to be used unless you chose to have `'django.contrib.auth',` in your `INSTALLED_APPS`.
|
|
|
|
|
|
Finally `'django.contrib.messages.middleware.MessageMiddleware',` is the middleware associated with `'django.contrib.sessions',` and will not be useful unless `'django.contrib.sessions',` is in your `INSTALLED_APPS`
|
|
|
|
|
|
A common `MIDDLEWARE` configuration for a micro-service could look like this:
|
|
|
|
|
|
`settings.py`
|
|
|
```
|
|
|
MIDDLEWARE = [
|
|
|
'django.middleware.security.SecurityMiddleware',
|
|
|
'django.middleware.common.CommonMiddleware',
|
|
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
|
]
|
|
|
|
|
|
if DEBUG is True:
|
|
|
MIDDLEWARE += [
|
|
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
|
]
|
|
|
```
|
|
|
|
|
|
### `TEMPLATES`
|
|
|
|
|
|
`TEMPLATES` is only used to render HTML pages which is very unlikely for most micro-services. You might need it though if you decided to use the admin or Django REST Framework's browsable interface when `DEBUG=True`
|
|
|
|
|
|
`TEMPLATES` in the default `settings.py` looks like this:
|
|
|
|
|
|
`settings.py`
|
|
|
```
|
|
|
...
|
|
|
TEMPLATES = [
|
|
|
{
|
|
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
|
'DIRS': [],
|
|
|
'APP_DIRS': True,
|
|
|
'OPTIONS': {
|
|
|
'context_processors': [
|
|
|
'django.template.context_processors.debug',
|
|
|
'django.template.context_processors.request',
|
|
|
'django.contrib.auth.context_processors.auth',
|
|
|
'django.contrib.messages.context_processors.messages',
|
|
|
],
|
|
|
},
|
|
|
},
|
|
|
]
|
|
|
...
|
|
|
```
|
|
|
|
|
|
You can see some `context_processors` are being loaded. Once again, some of those are linked to applications listed in `INSTALLED_APPS`. Namely `'django.contrib.auth.context_processors.auth',` and `'django.contrib.messages.context_processors.messages',` should be removed if you have decided to not list the corresponding applications in `INSTALLED_APPS`.
|
|
|
|
|
|
Another default that does not appear in this particular config are templates loader. Loaders are used to determine how to look for templates. The default value for this is:
|
|
|
|
|
|
```
|
|
|
'loaders': {
|
|
|
'django.template.loaders.filesystem.Loader',
|
|
|
'django.template.loaders.app_directories.Loader',
|
|
|
}
|
|
|
```
|
|
|
|
|
|
`'django.template.loaders.filesystem.Loader',` is used to load templates from the filesystem (ie: if you have a project-wide templates folder). This is most likely not the case for most micro-services, even when `DEBUG=True`, it can therefore be stripped as well.
|
|
|
|
|
|
A common `TEMPLATES` declaration for micro-services could look like this:
|
|
|
|
|
|
`settings.py`
|
|
|
```
|
|
|
if DEBUG is False:
|
|
|
TEMPLATES = []
|
|
|
else:
|
|
|
TEMPLATES = [
|
|
|
{
|
|
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
|
'DIRS': [],
|
|
|
'APP_DIRS': True,
|
|
|
'OPTIONS': {
|
|
|
'context_processors': [
|
|
|
'django.template.context_processors.debug',
|
|
|
'django.template.context_processors.request',
|
|
|
'django.contrib.messages.context_processors.messages',
|
|
|
],
|
|
|
'loaders': {
|
|
|
'django.template.loaders.app_directories.Loader',
|
|
|
}
|
|
|
},
|
|
|
},
|
|
|
]
|
|
|
```
|
|
|
|
|
|
### Misc.
|
|
|
|
|
|
Let's have a look at another few settings that can be turned off in most micro-services.
|
|
|
|
|
|
`USE_I18N` and `USE_L10N` are used for internationalization and localization. Most micro-services will not be serving translated or localized values, these two can therefore be set to `False` in most cases.
|
|
|
|
|
|
Similarly `USE_TZ` is used to deal with localized times. If your micro-service does not deal with dates or times, this setting can also be set to `False`
|
|
|
|
|
|
Here is what those could look like in a Django configuration for micro-services:
|
|
|
|
|
|
`settings.py`
|
|
|
```
|
|
|
USE_I18N = False
|
|
|
|
|
|
USE_L10N = False
|
|
|
|
|
|
USE_TZ = True
|
|
|
``` |