Business logic can be messy. In many applications, critical processes like calculating order totals, validating inventory, and processing payments are tangled together. This monolithic approach makes code difficult to test, a nightmare to maintain, and nearly impossible to reuse across different platforms like your web app, mobile app, and internal admin tools.
What if you could treat each piece of business logic as a simple, independent, and reusable building block?
This is the core philosophy behind function.do. By encapsulating your logic into discrete, atomic functions, you can compose them into powerful, automated services that are robust, scalable, and easy to manage.
In this post, we'll walk through a practical example: building a complete order processing workflow from scratch using atomic functions on the .do platform.
Before we build, let's understand the "why". An atomic function adheres to the single-responsibility principle: it does one thing and does it well.
This separation is key. When your logic is atomic, it becomes:
With function.do, these atomic functions aren't just abstract concepts; they are deployed as independent, callable API endpoints, turning your logic into a Business Logic API.
Every order workflow needs to calculate a total. Let's start by defining an agent to handle this. This OrderCalculator will contain a single function that takes a list of items and a tax rate and returns a full cost breakdown.
import { Agent, property } from '@do-sdk/core';
// Define the structure for a line item in an order
interface LineItem {
  productId: string;
  quantity: number;
  unitPrice: number;
}
// Create an agent that encapsulates the 'calculateOrderTotal' function
export class OrderCalculator extends Agent {
  
  @property()
  async calculateOrderTotal(
    items: LineItem[], 
    taxRate: number
  ): Promise<{ subtotal: number; tax: number; total: number }> {
    
    // Calculate the subtotal from the items
    const subtotal = items.reduce(
      (acc, item) => acc + item.quantity * item.unitPrice, 0
    );
    // Calculate tax and the final total
    const tax = subtotal * taxRate;
    const total = subtotal + tax;
    // Return the detailed breakdown
    return { subtotal, tax, total };
  }
}
With the @property() decorator, our calculateOrderTotal method is now a secure, executable endpoint on the .do platform. It's our first reusable building block.
Next, before we charge a customer, we must ensure the items are in stock. Let's create an InventoryValidator agent. Its sole purpose is to check our inventory system.
import { Agent, property } from '@do-sdk/core';
export class InventoryValidator extends Agent {
  @property()
  async checkStock(
    items: { productId: string; quantity: number }[]
  ): Promise<{ inStock: boolean; unavailable: string[] }> {
    const unavailableItems: string[] = [];
    
    // In a real scenario, this would query a database or inventory service
    for (const item of items) {
      const currentStock = await this.getInventoryLevel(item.productId); // Fictional helper
      if (currentStock < item.quantity) {
        unavailableItems.push(item.productId);
      }
    }
    return { inStock: unavailableItems.length === 0, unavailable: unavailableItems };
  }
  
  private async getInventoryLevel(productId: string): Promise<number> {
      // Dummy implementation for demonstration purposes
      console.log(`Checking stock for ${productId}...`);
      return 10; // Assume we have 10 of everything
  }
}
Now we have a second, independent function ready to be deployed. Notice how it has no knowledge of pricing or payments—it's perfectly atomic.
Individually, these functions are useful. Together, they form a powerful, automated service. This is where the magic of Composable Workflows on function.do comes in.
Let's create a final agent, OrderWorkflow, that orchestrates our atomic functions to process a complete order. The .do platform makes it seamless for one function to discover and call another.
import { Agent, property, service } from '@do-sdk/core';
import { OrderCalculator } from './OrderCalculator';
import { InventoryValidator } from './InventoryValidator';
// Imagine we also have a PaymentProcessor agent
import { PaymentProcessor } from './PaymentProcessor'; 
export class OrderWorkflow extends Agent {
  // The .do platform uses the @service() decorator to inject
  // instances of other deployed agents. No manual API clients needed!
  @service() private inventory: InventoryValidator;
  @service() private calculator: OrderCalculator;
  @service() private payment: PaymentProcessor;
  
  @property()
  async processOrder(
    items: LineItem[], 
    taxRate: number, 
    paymentToken: string
  ): Promise<{ success: boolean; message: string; orderId?: string }> {
    // 1. Validate Inventory by calling our first agent
    const stockCheck = await this.inventory.checkStock(items);
    if (!stockCheck.inStock) {
      return { success: false, message: `Items unavailable: ${stockCheck.unavailable.join(', ')}` };
    }
    // 2. Calculate Total by calling our second agent
    const orderTotals = await this.calculator.calculateOrderTotal(items, taxRate);
    // 3. Process Payment by calling a third agent
    const paymentResult = await this.payment.chargeCard(paymentToken, orderTotals.total);
    if (!paymentResult.success) {
      return { success: false, message: `Payment failed: ${paymentResult.error}` };
    }
    // 4. Finalize the order
    const orderId = `ord_${Date.now()}`;
    // (Here you could call another function to save the order to a DB and send a confirmation email)
    
    return { success: true, message: 'Order processed successfully!', orderId };
  }
}
Look at what we just did. We built a sophisticated order processing service not by writing a monolith, but by composing three simple, independent functions. The OrderWorkflow agent doesn't contain any complex business logic itself; it acts as a conductor, orchestrating the calls to the specialized agents that do.
This approach, which function.do is built for, provides tangible benefits over traditional serverless functions or monolithic code:
From simple logic to a complete service, function.do gives you the tools to build with clarity and confidence. You get all the power of a complex microservices architecture with the simplicity of writing functions.
Ready to transform your tangled business logic into clean, composable services? Explore function.do today and start building with atomic functions for infinite possibilities.