Composing a Domain

A domain in Protean represents a Bounded Context of the application. Because it is aware of all domain elements, the protean.Domain in Protean acts as a Composition Root, with which all modules are composed together. It is responsible for creating and maintaining a graph of all the domain elements in the Bounded Context.

The Domain is the one-stop gateway to:

  • Register domain elements

  • Retrieve dynamically-constructed artifacts like repositories and models

  • Access injected technology components at runtime

Define a Domain

Constructing the object graph is a two-step procedure. First, you initialize a domain object at a reasonable starting point of the application.

from protean import Domain
domain = Domain(__name__)

Registering Elements to the Domain

Next, the domain object is referenced by the rest of the application to register elements and participate in application configuration.

@domain.aggregate
class User:
    name = String()
    email = String(required=True)

Initializing the Domain

Finally, the domain is initialized by calling the init method. This method will construct the object graph and inject dependencies into the domain elements.

domain.init()

By default, a protean domain is configured to use an in-memory repository. This is useful for testing and prototyping.

If you do not want Protean to traverse the directory structure to discover domain elements, you can pass the traverse flag as False to the init method.

You can optionally pass a config file to the domain before initializing it. Refer to Configuration Handling to understand the different ways to configure the domain.

Activating a Domain

A domain is activated by pushing up its context to the top of the domain stack.

context = domain.domain_context()
context.push()

Subsequent calls to protean.globals.current_domain will return the currently active domain. Once the task has been completed, it is recommended that the domain stack be reset to its original state by calling context.pop().

This is a convenient pattern to use in conjunction with most API frameworks. The domain’s context is pushed up at the beginning of a request and popped out once the request is processed.

When to compose

The composition should take place as close to the application’s entry point as possible. In simple console applications, the Main method is a good entry point. But for most web applications that spin up their own runtime, we depend on the callbacks or hooks of the framework to compose the object graph.

Accordingly, depending on the software stack you will ultimately use, you will decide when to compose the object graph. For example, if you are using Flask as the API framework, you would compose the domain along with the app object.

import logging.config
import os

from flask import Flask

from sample_app import domain

def create_app():
    app = Flask(__name__, static_folder=None)

    # Configure domain
    current_path = os.path.abspath(os.path.dirname(__file__))
    config_path = os.path.join(current_path, "./../config.py")
    domain.config.from_pyfile(config_path)

    logging.config.dictConfig(domain.config['LOGGING_CONFIG'])

    from api.views.registration import registration_api
    from api.views.user import user_api
    app.register_blueprint(registration_api)
    app.register_blueprint(user_api)

    @app.before_request
    def set_context():
        domain.init()

        # Push up a Domain Context
        # This should be done within Flask App
        context = domain.domain_context()
        context.push()

    return app

Of note is the activation of the domain with the help of @app.before_request decorator above - this is Flask-specific. Refer to adapter-api section to understand how to accomplish this for other frameworks.