Modern applications are rarely simple. A single user action, like signing up or placing an order, can trigger a cascade of events: databases need updating, emails must be sent, inventory gets reserved, and third-party APIs are called. Managing this complexity is one of the biggest challenges in software development today.
The traditional approach often involves a jungle of tangled function calls, custom pub/sub systems, and hard-to-follow logic scattered across a codebase. This makes systems brittle, difficult to debug, and a nightmare to audit. What if there was a better way?
What if you could define each step of a process as a distinct, declarative action and then simply chain them together? This is the core idea behind Business-as-Code, and it's precisely what Verbs.do's effects feature is designed for. In this post, we'll explore how you can simplify your most complex business processes by chaining actions to create clear, decoupled, and auditable workflows.
Let's consider a common workflow: a new user signing up. In a monolithic application, the signUp function might look something like this (in pseudo-code):
function handleSignUp(email, password) {
// 1. Validate input
// 2. Hash password
// 3. Create user record in the database
try {
// 4. Send a welcome email
emailService.sendWelcomeEmail(email);
} catch (error) {
// What do we do now? The user is created but got no email. Log it? Retry?
}
// 5. Add user to the marketing CRM
crmService.addNewContact(email);
// 6. Return success to the user
}
This code works, but it has several critical flaws:
Verbs.do reframes the problem. Instead of burying business logic in functions, you define them as standardized, observable Verbs. A Verb is a formal definition of a business action.
Let's look at how we'd define our SignUp action.
import { Verb } from 'verbs.do';
// Define a 'SignUp' action once
const signUp = new Verb({
name: 'SignUp',
description: 'A new user creates an account.',
subject: { type: 'User' },
properties: {
email: { type: 'string', format: 'email' },
password: { type: 'string', sensitive: true }
},
effects: [
{ name: 'CreateUserRecord' },
{ name: 'SendWelcomeEmail' }
]
});
This definition is clear and self-documenting. It specifies what a SignUp is, who performs it (a User), and what data it requires (properties).
But the real power lies in that final array: effects.
The effects array is a declarative list of other actions that should be triggered after the SignUp Verb is successfully executed. This is the heart of workflow automation in Verbs.do.
When you execute the SignUp Verb...
// Execute it anywhere in your application
await signUp.execute({
subject: { id: 'user_123' },
properties: {
email: 'hello@example.com',
password: 'a-secure-password'
}
});
...the Verbs.do platform not only logs the SignUp action but also automatically queues the CreateUserRecord and SendWelcomeEmail actions to run next. This has profound benefits for your architecture.
The code that triggers the SignUp action knows nothing about sending emails or creating database records. Its only job is to execute the SignUp Verb. The implementation of SendWelcomeEmail lives somewhere else entirely, defined as its own Verb.
Want to add a new step to your onboarding, like adding the user to a CRM? You don't have to touch your application code. You simply update the SignUp Verb's definition:
const signUp = new Verb({
// ... other properties
effects: [
{ name: 'CreateUserRecord' },
{ name: 'SendWelcomeEmail' },
{ name: 'AddUserToCRM' } // Just add the new effect here!
]
});
This makes your system incredibly flexible and easy to maintain.
This is where Verbs.do truly shines. Because every step in the workflow is its own Verb execution, the platform automatically generates a complete, causally-linked log. Answering the question "What happened when user_123 signed up?" is no longer a matter of grepping through log files. Your system of record will show a clear sequence:
This immutable event log provides unparalleled visibility, simplifying debugging, compliance, and security analysis. It’s an Event Sourcing API for your entire business.
The effects are typically handled asynchronously. This means the initial signUp.execute() call can return a success message to the user instantly, while the subsequent, longer-running tasks (like sending an email) are handled by the Verbs.do platform in the background. The platform can manage retries and error handling for these side-effects, making your entire workflow more resilient.
This pattern of action management scales beautifully. Imagine an e-commerce PlaceOrder Verb:
const placeOrder = new Verb({
name: 'PlaceOrder',
subject: { type: 'Customer' },
object: { type: 'Cart' },
properties: { total: 100.00, currency: 'USD' },
effects: [
{ name: 'ProcessPayment' },
{ name: 'ReserveInventory' },
{ name: 'SendOrderConfirmation' }
]
});
The ProcessPayment Verb could even have its own effects, such as GenerateInvoice and NotifyFinanceTeam. By composing simple, single-responsibility Verbs, you can model and execute even the most complex business processes with clarity and confidence.
If your codebase is weighed down by tangled business logic, it's time for a new approach. By treating actions as first-class citizens, Verbs.do allows you to move from imperative code to a declarative system. The effects feature is your key to unlocking simple, powerful, and observable workflow automation.
You can build systems that are easier to maintain, automatically produce a perfect audit trail, and are resilient by design.
Ready to transform your operations into code? Explore the Verbs.do API and see how defining, executing, and logging actions can revolutionize your architecture.