Skip to content

Instantly share code, notes, and snippets.

@dgabriele
Created February 4, 2020 20:04
Show Gist options
  • Save dgabriele/c5465351a911d59fe4fdd400a880878d to your computer and use it in GitHub Desktop.
Save dgabriele/c5465351a911d59fe4fdd400a880878d to your computer and use it in GitHub Desktop.

Ravel Service Layer

The service layer is conceptually similar to a web framework in that there are apps, endpoints, middleware, requests and responses. As a compared to a web framework, however, Ravel has a more abstract notion of what these things are. Ultimately, the service layer presents a public interface to the outside world.

Service Architecture

As mentioned before, the architecture of the service layer is a lot like a web framework, but there are some important additions--namely, stages 2, 3, and 7 below.

Stage 1: Pre-request Middleware

In the first stage, middleware has a chance to initiate services and access the arguments with which an API endpoint was called. Note that arguments may differ, depending on the type of Ravel application being run. In a web app, these arguments might consist of an HTTP request; for a gRPC service, they'd consist of protocol buffer messages.

Stage 2: Argument Extraction

In stage two, Ravel inspects the arguments with which an endpoint was called. It then constructs the positional and keyword arguments expected by the function targeted by the endpoint.

Example: Extracting Arguments From HTTP Requests

Imagine a signup function that we'd like to reuse as an endpoint in a Ravel WSGI app. The Application instance in this example is called web. Under the hood, a web framework is running that provides only HTTP request as arguments. In contrast, our imaginary function expects something entirely different: namely, email and password.

@web(method='POST')
def signup(email: str, password: str) -> User:
    ...

The web framework does something like this:

web.api.signup(request)

As you may have guessed, values for email and password must be extracted from the raw HTTP request.

Stage 3: Argument Resolution & Normalization

Once extraction is complete, the next stage it to replace arguments which refer to application Resource objects (like users, accounts, etc.) by ID with the objects themselves. This process is based on the Python type annotations provided for endpoint arguments and return values.

Example: Resolving a user Argument

Suppose we have a login endpoint with a user argument, annotated as a Ravel User resource.

@app(method='POST')
def login(user: User, password: str) -> User:
    ...

After resolving the user argument, all of the following API calls are equivalent:

Passing a User Resource
app.api.login(user, password)
Passing a User ID
app.api.login('USER-123', password)
Passing a dict
app.api.login({'_id': 'USER-123'}, password)

Stage 4: On-request Middleware

At this point middleware can hook into the resolved arguments ready to be passed into the endpoint function.

Stage 5: Target Function Execution

With the prepared arguments in hand, we finally call the endpoint function. If you are using a web framework through Ravel, note that all of its middleware runs within this stage.

Stage 6: Post-request Middleware

In post-request, we can perform and cleanup or finalization of middleware services initiated in pre-request or on-request. In addition, we can inspect and alter the raw value returned by the endpoint function.

Stage 7: Response Marshaling

The last stage involves generating a final output value. In web apps, for instance, the final value is an HTTP response object.

Ravel apps are configured, initialized, and run by instances of the Application class. These objects provide centralized access to endpoints, data stores, and resource types. In addition, they expose application endpoints through an API.

Available Application Types

In addition to the Application base class, Ravel comes with several prepackaged extensions.

Endpoints make up the public API of Ravel apps and are registered via decorator, like so:

app = Application()

@app()
def my_endpoint(arg1, arg2):
    ...

After bootstrapping an application, endpoints are accessible through an api property.

app.api.my_endpoint('arg1', 'arg2')

Middleware provides additional services at key points while processing requests. Like traditional middleware in web frameworks, Ravel middleware can do things like manage database transactions and trace requests. In contrast, Ravel middleware can be reused in radically different computing contexts. For example, the same middleware for database transaction management can be used while calling an API method in a REPL as though HTTP.

Ravel Request objects hold references the arguments supplied to endpoints. They accumulate additional state of various kinds during endpoint execution.

Key Attributes

  • raw_args, raw_kwargs - Positional and keyword arguments with which the endpoint was called.
  • prepared_args, prepared_kwargs - Argument values generated by the Argument Resolution execution stage.

Ravel Response objects hold references to both the raw values returned by endpoints and the result of processing said value in order to prepare and marshal it out.

Key Attributes

  • raw_result - The raw value returned by the endpoint function.
  • prepared_result - The processed value, ready to be marshaled out by the host Application.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment