Moving to Wyoming

Steve Howard
steve@thumbtack.com

9/5/2013

Dependency management is really important

What's a dependency?

Ways to manage dependencies

Our example case

my_app.py

class MyApp(object):
    def get_response(self, request):
        return Response(somehow render('hello.html', name=request.args['name']))

Static cling

templates.py

import jinja2

jinja_environment = jinja2.Environment(
        loader=jinja2.PackageLoader('my_package', 'templates'))

def render(template_name, **context):
    return jinja_environment.get_template(template_name).render(**context)

my_app.py

import templates

class MyApp(object):
    def get_response(self, request):
        return Response(templates.render('hello.html',
                                         name=request.args['name']))

main.py

import my_app

def main():
    app = my_app.MyApp()
    ...

Singleton

templates.py

import jinja2

class TemplateManager(object):
    def __init__(self):
        self._environment = jinja2.Environment(
            loader=jinja2.PackageLoader('my_package', 'templates'))

    def render(self, template_name, **context):
        return self._environment.get_template(template_name).render(**context)

manager = TemplateManager()

my_app.py

import templates

class MyApp(object):
    def get_response(self, request):
        return Response(
            templates.manager.render(
                'hello.html', name=request.args['name']))

main.py

import my_app

def main():
    app = my_app.MyApp()
    ...

Dependency injection

templates.py

class TemplateManager(object):
    def __init__(self, jinja_environment):
        self._environment = jinja_environment

    def render(self, template_name, **context):
        return self._environment.get_template(template_name).render(**context)

my_app.py

class MyApp(object):
    def __init__(self, template_manager):
        self._template_manager = template_manager

    def get_response(self, request):
        return Response(
            self._template_manager.render(
                'hello.html', name=request.args['name']))

main.py

import jinja2, my_app, templates

def main():
    template_manager = templates.TemplateManager(
        jinja2.Environment(
            loader=jinja2.PackageLoader('my_package', 'templates')))
    app = my_app.MyApp(template_manager)
    ...

DI >> Singleton >> Static cling

What's so great here?

Movin' out west

From static cling to singleton

Step 1: Make one shared place for all external dependencies

wyoming.py

# The SQLAlchemy Session class
session_factory = None

# The templates.TemplateManager
template_manager = None

...

From static cling to singleton

Step 2: Initialize that shared place when your app starts

main.py

import my_app
import templates
import wyoming

def initialize_wyoming():
    engine = sqlalchemy.create_engine(...)
    models.Base.metadata.create_all(engine)
    wyoming.session_factory = sqlalchemy.orm.sessionmaker(bind=engine)

    jinja_environment = jinja2.Environment(
        loader=jinja2.PackageLoader('my_package', 'templates'))
    wyoming.template_manager = templates.TemplateManager(jinja_environment)

    ...

def main():
    initialize_wyoming()
    app = my_app.MyApp()
    ...

From static cling to singleton

Step 3: Update all references in your app to point to wyoming

my_app.py

import wyoming

class MyApp(object):
    def get_response(self, request):
        session = wyoming.session_factory()
        results = session.query(...)
        return Response(
            wyoming.template_manager.render(
                'hello.html', name=request.args['name'],
                results=results))

Never put new dependencies into global state!

From singleton to dependency injection

Step 1: Move global access to constructors

my_app.py

import wyoming

class MyApp(object):
    def __init__(self):
        self._session_factory = wyoming.session_factory
        self._template_manager = wyoming.template_manager

    def get_response(self, request):
        session = self._session_factory()
        results = session.query(...)
        return Response(
            self._template_manager.render(
                'hello.html', name=request.args['name'],
                results=results))

From singleton to dependency injection

Step 2: Move global access to instantiation sites

my_app.py

class MyApp(object):
    def __init__(self, session_factory, template_manager):
        self._session_factory = session_factory
        self._template_manager = template_manager

    def get_response(self, request):
        session = self._session_factory()
        results = session.query(...)
        return Response(
            self._template_manager.render(
                'hello.html', name=request.args['name'],
                results=results))

main.py

import my_app
import wyoming

def initialize_wyoming(): ...

def main():
    initialize_wyoming()
    app = my_app.MyApp(wyoming.session_factory, wyoming.template_manager)
    ...

From singleton to dependency injection

Step 3: Notice you no longer need wyoming

main.py

def main():
    engine = sqlalchemy.create_engine(...)
    models.Base.metadata.create_all(engine)
    session_factory = sqlalchemy.orm.sessionmaker(bind=engine)

    jinja_environment = jinja2.Environment(
        loader=jinja2.PackageLoader('my_package', 'templates'))
    template_manager = templates.TemplateManager(jinja_environment)

    app = my_app.MyApp(session_factory, template_manager)

Farewell, wyoming!

Standing on the shoulders of giants

Questions?

steve@thumbtack.com