Skip to content

Instantly share code, notes, and snippets.

@dgabriele
Last active February 5, 2020 19:45
Show Gist options
  • Save dgabriele/0f0528600fcbe90a64fd25218421697c to your computer and use it in GitHub Desktop.
Save dgabriele/0f0528600fcbe90a64fd25218421697c to your computer and use it in GitHub Desktop.

Ravel Business Logic Layer

What an app does is, by definition, its business logic. This layer is sandwiched between the service layer, which communicates with the outside world, and the data access layer (DAL), which communicates with the database.

One of our primary goals when creating Ravel was to give ourselves a way to write scalable and extensible code that requires only as much effort as it takes to write pseudocode on a napkin. In Ravel, new apps and features can be fully expressed without a database or the need for manual fixture generation. Frontend development is therefore immediately unblocked, as incidental complexity on the backend is minimized.

Understanding the business logic layer is essential for harnessing Ravel's most powerful features.

Example: Users & Accounts

Imagine an app where there are users and accounts to which users belong. The class definitions might looks something like this:

class Account(Resource):
    name = String(required=True)
    
class User(Resource):
    account_id = Id(Account, required=True)
    is_verified = Bool(required=True, default=False)
    email = Email(required=True)

At this point, we already have enough structure to build complex behaviors in an app. For instance, here is an endpoint that returns all verified users of an account:

@app()
def get_verified_users(account: Account) -> List[User]:
    return User.select(
        User
    ).where(
        User.is_verified == True,
        User.account_id == account._id
    ).execute()

All of this works out-of-the-box. Without any configuration, Ravel simulates an data store in memory, implementing efficient algorithms to support ad hoc queries through a flexible SQL-like interface.

One Step Further

If this query for verified users turns out to be used all over, it is possible to turn it into a selectable attribute on Account, using a "join" resolver. Without going into detail, this would look something like this:

class Account(Resource):
    name = String(required=True)
    
    @join(right=User.account_id, many=True)
    def verified_users(self, join, query):
        return query.where(User.is_verified == True).execute()

Now the get_verified_users endpoint can be rewritten in a simpler form.

@app()
def get_verified_users(account: Account) -> List[User]:
    return account.verified_users

All of this and more is within the purview of the business logic layer.

At a high-level, an application resource is a program entity that participates in business logic. In code, this word corresponds to any class that derives from Resource, out of which such entities are built. Users, accounts, sparse matrices, etc. are all instances of resource objects.

Example: Basic "Particle" Resource

The most elementary form of a resource is one with only a handful of simple fields. Here is a "particle" object from a physics simulation:

class Particle(Resource):
    name = String()
    mass = Float()
    diameter = Float()
    charge = Float()
    spin = String()

Any attribute on a resource that a developer can select in a query is managed by a resolver. Resolvers are responsible for loading data and provide callbacks for certain application events, like dumping and saving. Understanding resolvers is important for writing DRY code and taking advantage of Ravel's advanced query methods.

Example: Resolving Weather

The base Resolver can be used to create new selectable attributes on resource objects, regardless of where the data comes from. For example, here's a Place resource from a geolocation app, where the weather attribute is implemented with a custom resolver that uses an HTTP client to request a WeatherReport from an 3rd-party service.

class Place(Resource):

    @resolver
    def weather(self, resolver, query) -> WeatherReport:
        return self.weather_client.get(location=query.params.location)

Queries are a fundamental building block of Ravel apps.The syntax of Ravel queries is inspired by SQLAlchemy, a popular Python ORM; however, they are much more customizable. Each Ravel query is a singular specification that nevertheless pulls data from multiple different data stores simultaneously, as if it were all stored in one place. Understanding the basics is essential, not to mention extremely useful when it comes to writing tests.

Example: Basic Query

Select a user's email address by their user ID.

query = User.select(
    User.email
).where(
    User._id == user_id
)

Example: Query With Nested Select

query = User.select(
    User.email,
    User.account.select(
        Account.name
    )
).where(
    User.member_since >= cutoff_date
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment