In the world of workflow automation, the ultimate goal is to build robust, scalable, and complex systems that just work. But as complexity grows, so does the potential for failure. The secret to managing this complexity isn't building a bigger, more monolithic system; it's about breaking it down into the smallest, most reliable pieces possible.
This is where the concept of an atomic action comes into play. In the .do ecosystem, this fundamental building block is action.do.
An action.do represents a single, indivisible, and executable step in a larger process. Think of it as one verb, one task, one specific outcome. It’s not "Onboard a New User"; it’s "Send a Welcome Email" or "Create a Database Record."
This atomicity is its superpower. Because each action does only one thing, it becomes incredibly:
Here’s how simple defining an action can be with the .do SDK:
import { action } from '@do-sdk/core';
// Define an action to send a welcome email
const sendWelcomeEmail = action.create({
id: 'send-welcome-email',
description: 'Sends a welcome email to a new user.',
execute: async ({ email, name }) => {
// Your email sending logic via an external API
console.log(`Sending welcome email to ${name} at ${email}...`);
// On success, return a meaningful result
return { success: true, messageId: 'xyz-123' };
}
});
// Execute the action with specific inputs
const result = await sendWelcomeEmail.execute({
email: 'jane.doe@example.com',
name: 'Jane Doe'
});
This sendWelcomeEmail action is a perfect, self-contained unit. It takes an email and a name, performs its task, and returns a result. But notice what it doesn't do: it doesn't remember if it ran before.
By design, action.do units are stateless. They receive input, perform their task, and produce output without retaining any memory of previous executions. Given the same input, a stateless action will always attempt to produce the same output.
This is a cornerstone of modern, resilient software design. It prevents unpredictable side effects and makes your automation predictable and easy to debug.
But this leads to a critical question: If every step is stateless, how do you build a stateful workflow?
Real-world processes inherently require state. You need to know:
This is the stateless paradox: we need memoryless components to build a memorable process.
The .do platform solves this elegantly by separating concerns.
While individual actions are stateless, the workflow.do that orchestrates them is stateful. It acts as the project manager, keeping track of the overall progress of a job. And its notebook—where it tracks this progress—is the .do Datastore.
The Datastore is a key-value store designed specifically for persisting state across workflow executions. Your workflow can read from the Datastore to decide which action to run next and write to it to record what has been accomplished.
Let's revisit our sendWelcomeEmail action and put it into a larger onboarding workflow.do.
Initial State: A user signs up. Our workflow creates an initial record in the .do Datastore.
{
"userId": "usr_12345",
"status": "registered",
"welcomeEmailSent": false,
"profileSetup": false
}
Execute Action: The workflow reads this state and sees welcomeEmailSent is false. It triggers the stateless sendWelcomeEmail action, passing the user's email and name as input.
Update State: Upon successful execution of the action, the workflow updates the record in the Datastore.
{
"userId": "usr_12345",
"status": "email_sent",
"welcomeEmailSent": true,
"profileSetup": false
}
Next Step: The workflow can now proceed. Perhaps it waits for a webhook indicating the user has clicked a link in the email, or it triggers another action, like createInitialUserProfile. Because the state is safely persisted, the workflow can even pause for hours or days and resume exactly where it left off.
If the sendWelcomeEmail action had failed (perhaps the email API was temporarily down), the state would remain unchanged. The workflow could then implement a retry strategy, attempting the action again later without losing track of what needs to be done.
This architecture gives you the clarity of stateless functions and the power of stateful orchestration:
By embracing stateless atomic actions and providing a dedicated Datastore for managing state at the workflow level, the .do platform empowers you to move beyond simple scripts. You can start composing simple actions into truly robust, enterprise-grade automated services, turning your business logic into valuable Services-as-Software.