Mutations is a Python package I just released for handling, validating, and executing complex business logic in web applications. I’ve used it in a large Django codebase to shrink large models and move complex logic into independent, testable, and reusable command classes. It's based on the Ruby eponym, Jonathan Novak's excellent mutations gem.
Why Use Mutations?
- Keep Models & Views Thin: Mutations is a library for your business logic. It encourages you to keep business logic in a dedicated location instead of deeply boring it in model or view code.
- Makes Key Transactions Testable: Commands in Mutations simple to test; they’re regular Python classes. You can easily test them using unittest, pytest, or your favorite test runner. Easy test setup means your team can focus on edge cases that impact your customers; not getting your test suite to simulate an rare edge condition.
- Validates User Inputs: Mutations comes with a set of input validators, so you get input validation for free on all of your commands. For example, you can define character, email,
dict, and essentially any other sort of field. Expecting an email address? There’s a validator for that.
- Modular: If you’re doing similar transactions over and over again, you can factor out shared logic and keep it one place so that you don’t repeat yourself.
- Extensible: You can write custom validations to test against any condition. Moreover, the core library is ~200 lines of code, so it’s easy to fork and make changes to fit any bespoke needs.
What is Business Logic?
Business logic: rules and conditions that run your app/business and make the user’s experience predictable and streamlined.
As an example (in a web application): when a user signs up, send them a welcome email, except if they signed up with their Twitter account, in which case send them a twitter DM. Lots of these small decisions and policies add up and sometimes lead to very large models.
This logic is simple in the beginning, but can quickly grow as your business needs evolve, your previously simple logic gets complex, very quickly. For example, if the new user comes through a partner’s channel, make an API request to that partner’s backend and see if they should get an email or not. Sometimes, the partner's backend is down, in which case enqueue the request and try again in five minutes.
Complexity increases quickly, and suddenly you find yourself enqueueing retry requests to your partner’s backend in a class that also handles storing the user’s email and password. With this we face a new question: where should this code live?
Store Logic in Separate Command Classes
I wrote Mutations as an answer to this question: store the business logic in separate, reusable, and testable classes. Classes with fewer responsibilities are generally easier to maintain, test, and debug, and pulling transaction logic out of models and into something else keeps your models focused on what they do best: defining and persisting data.
Here’s an example of how you could use it to handle the aforementioned example:
You can then call the command like this:
Say, for example, you need to run this command one-off, or on a batch of users on their behalf. With mutations, you can run the signup command in a regular console, instead of having to sign users up through the web application on their behalf.
Moreover, you can validate against any condition. Any method inside a Mutation class that’s prefixed with
validate_ will be treated as a validation method and will be run before the command can be executed. For example:
Mutations helps you split out your business logic and run it in separate, reusable, testable command classes. It keeps your models thin, key commands testable, is modular, and extensible. Give it a try — it’s available on GitHub and PyPi, and tweet @omarish with any feedback/suggestions.