Blog

 

 

Now we know how we'll build our application, let's discuss the individual services, starting with the REST API. At the top level of the rest_api directory we have a folder containing the actual application, a Dockerfile and a Conda environment file. Conda is a language-agnostic package, dependency and environment manager. We're using it to handle the installation of Python and a number of dependencies:

name: rest-api-env

channels:
  - defaults
  - conda-forge
dependencies:
  - python=3.6
  - flask=1.0.2
  - gunicorn=19.9
  - pip

The channels specify where Conda should look to find packages we wish to install, and then actual dependencies are listed below. Specific versions are stated to avoid installing newer versions of dependencies with code-breaking changes. You'll notice that pip, the built-in Python package manager is installed; this is because a number of dependencies aren't available through Conda.

Next we have the Dockerfile:

FROM continuumio/miniconda3

COPY rest-api-env.yml /tmp/rest-api-env.yml
RUN conda env create -f /tmp/rest-api-env.yml

COPY rest_api /src/rest_api/rest_api/

RUN echo "source activate rest-api-env" > ~/.bashrc
ENV PATH /opt/conda/envs/rest-api-env/bin:$PATH

RUN pip install flask-rebar==1.1.0
RUN pip install marshmallow==2.16.3

Firstly we start with a pre-defined image that allows us to use Conda. Then we copy our Conda environment file into a temporary directory and run the command to create a Conda environment using this file. After the environment has resolved we copy our application code into the intended directory and activate the newly created Conda environment. Lastly, within the environment, we install Flask-Rebar and Marshmallow, these Python packages form the basis of our REST API.

This leaves the application code. To clarify exactly where the application code lives, here's the directory structure solely containing the application:

rest_api/
    - rest_api/
        - init.py
        - routes.py
    - wsgi.py

Let's start with wsgi.py:

from rest_api import app

if name == "main":
    app.run(host='0.0.0.0', port=80, debug=True)

The file wsgi.py simply imports the app object from the rest_api module. The conditional statement is simply there if we wish to manually run the application, say for debugging purposes. The app object is used by gunicorn, specified in the docker-compose.yml file.

Then we have the two files contained in the final rest_api directory. The presence of an init.py file indicates that this is a Python package. This can, when not needed, be an empty file, however it is used here to create the app object used by gunicorn and make the application aware of all API endpoints registered by Flask-Rebar. Here is init.py:

from rest_api.routes import rebar
from flask import Flask

app = Flask(name)
rebar.init_app(app)

import rest_api

Lastly, we have routes.py:

from flask import current_app, request
from flask_rebar import Rebar, response


rebar = Rebar()
registry = rebar.create_handler_registry()


@registry.handles(
    rule='/',
    method='GET'
)
def get_index():
    return response(
        data={'message': 'Hello, Being!'},
        status_code=200
    )

Within routes.py I simply specify a simple GET request endpoint and add it to the Flask-Rebar registry; analogous to using @app.route in vanilla Flask.

So we've covered the container and environment configuration for the API as well as it's source code, let's move onto the Nginx container.