logo

azure-pipeline codecov docs gitter pypi black


The business transaction DSL

stories is a business transaction DSL. It provides a simple way to define a complex business transaction that includes processing over many steps and by many different objects. It makes error handling a primary concern by taking a “Railway Oriented Programming” approach to capturing and returning errors from any step in the transaction.

stories is based on the following ideas:

  • A business transaction is a series of operations where any can fail and stop the processing.
  • A business transaction can describe its steps on an abstract level without being coupled to any details about how individual operations work.
  • A business transaction doesn’t have any state.
  • Each operation shouldn’t accumulate state, instead it should receive an input and return an output without causing any side-effects.
  • The only interface of an operation is ctx.
  • Each operation provides a meaningful piece of functionality and can be reused.
  • Errors in any operation should be easily caught and handled as part of the normal application flow.

Example

stories provide a simple way to define a complex business scenario that include many processing steps.

>>> from stories import story, arguments, Success, Failure, Result
>>> from django_project.models import Category, Profile, Subscription

>>> class Subscribe:
...
...     @story
...     @arguments('category_id', 'profile_id')
...     def buy(I):
...
...         I.find_category
...         I.find_profile
...         I.check_balance
...         I.persist_subscription
...         I.show_subscription
...
...     def find_category(self, ctx):
...
...         category = Category.objects.get(pk=ctx.category_id)
...         return Success(category=category)
...
...     def find_profile(self, ctx):
...
...         profile = Profile.objects.get(pk=ctx.profile_id)
...         return Success(profile=profile)
...
...     def check_balance(self, ctx):
...
...         if ctx.category.cost < ctx.profile.balance:
...             return Success()
...         else:
...             return Failure()
...
...     def persist_subscription(self, ctx):
...
...         subscription = Subscription(category=ctx.category, profile=ctx.profile)
...         subscription.save()
...         return Success(subscription=subscription)
...
...     def show_subscription(self, ctx):
...
...         return Result(ctx.subscription)
>>> Subscribe().buy(category_id=1, profile_id=2)
<Subscription: Subscription object (9)>

This code style allow you clearly separate actual business scenario from implementation details.

Note

stories library was heavily inspired by dry-transaction ruby gem.