The Functional Resource pattern exposes command-style, compute-style, or decision-style operations that do not represent a resource lifecycle.

These operations take an input, perform work, and return an output, without implying CRUD behavior or lifecycle transitions.

6.1. Overview

Functional Resources:

  • Represent business operations, not resources
  • Are often pure functions (input → output)
  • Do not create long-lived entities
  • Do not require CRUD semantics or state transitions
  • Are named using verb–noun or action-oriented terms

This pattern is ideal for calculations, simulations, estimates, and decisioning operations.

These endpoints are named using a verb–noun structure such as:

  • POST /estimate-tax
  • POST /calculate-shipping
  • POST /check-eligibility
  • POST /compute-loan-schedule

6.2. When to Use the Functional Resource Pattern

Use this pattern when the API is:

  • Performing a calculation

    • POST /estimate-tax
  • Generating a quote or estimate (nondurable)

    • POST /calculate-shipping
  • Checking conditions or eligibility

    • POST /check-eligibility
  • Computing financial schedules

    • POST /compute-loan-schedule
  • Combining multiple services to produce a derived value

These operations:

  • Are not CRUD
  • Don’t create or store a persistent object
  • Don’t move a resource through a lifecycle (which would be Extended CRUD)

6.3. When NOT to Use This Pattern

Avoid it when:

  • The result will become a first-class resource

    • e.g., a “quote” that needs to be retrieved, updated, and accepted
  • A resource has a real lifecycle

    • e.g., a document moving through Draft → Submitted → Approved
  • The domain action represents a state transition

    • e.g., canceling an order → Extended CRUD
  • You are trying to avoid designing a real resource by hiding it behind a function

Decision rule: Use Functional Resource for commands without lifecycle. Use Extended CRUD for lifecycles with transitions.

6.4. What the Pattern Looks Like

A typical Functional Resource endpoint:

POST /estimate-tax
POST /calculate-shipping
POST /compute-interest
POST /check-eligibility

Characteristics:

  • Expressed as a top-level command
  • No resource ID in the path
  • Self-contained request + response
  • Stateless from the client’s perspective
  • No CRUD or lifecycle implied

6.5. Anti-Patterns to Avoid

❌ 1. Modeling compute operations as CRUD

POST /tax-estimates
GET /tax-estimates/{id}

This adds lifecycle that does not exist.

❌ 2. Embedding compute triggers in PATCH requests

PATCH /orders/{id} { "recalculateTax": true }

Confusing and hides behavior.

❌ 3. Using Extended CRUD naming for commands

POST /orders/{id}/estimate-tax

This incorrectly suggests tax estimation is part of the Order lifecycle.

✅ Correct form:

POST /estimate-tax

This communicates a pure computation.

6.6. OpenAPI Example

openapi: 3.0.3
info:
  title: Tax Estimation API - Functional Resource Pattern Example
  version: 1.0.0
servers:
  - url: https://api.example.com

paths:
  /estimate-tax:
    post:
      summary: Estimate sales tax for a set of items
      tags: [Tax]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TaxEstimateRequest'
            examples:
              default:
                value:
                  currency: "USD"
                  destination:
                    country: "US"
                    postalCode: "94103"
                    region: "CA"
                  items:
                    - sku: "BOOK-DDD-001"
                      quantity: 1
                      unitPrice: 5500
                      taxCategory: "BOOK"
      responses:
        '200':
          description: Tax estimated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TaxEstimateResult'
              examples:
                default:
                  value:
                    currency: "USD"
                    totalTaxAmount: 950
                    items:
                      - sku: "BOOK-DDD-001"
                        quantity: 1
                        lineTaxAmount: 400
                      - sku: "BOOK-API-001"
                        quantity: 2
                        lineTaxAmount: 550

components:
  schemas:
    TaxEstimateRequest:
      type: object
      properties:
        currency:
          type: string
          example: "USD"
        destination:
          type: object
          properties:
            country:
              type: string
              example: "US"
            postalCode:
              type: string
              example: "94103"
            region:
              type: string
              example: "CA"
          required: [country, postalCode]
        items:
          type: array
          items:
            $ref: '#/components/schemas/TaxEstimateItem'
      required: [currency, destination, items]

    TaxEstimateItem:
      type: object
      properties:
        sku:
          type: string
          example: "BOOK-DDD-001"
        quantity:
          type: integer
          example: 1
        unitPrice:
          type: integer
          example: 5500
        taxCategory:
          type: string
          example: "BOOK"
      required: [sku, quantity, unitPrice]

    TaxEstimateResult:
      type: object
      properties:
        currency:
          type: string
          example: "USD"
        totalTaxAmount:
          type: integer
          example: 950
        items:
          type: array
          items:
            $ref: '#/components/schemas/TaxEstimateLineResult'
      required: [currency, totalTaxAmount, items]

    TaxEstimateLineResult:
      type: object
      properties:
        sku:
          type: string
          example: "BOOK-DDD-001"
        quantity:
          type: integer
          example: 1
        lineTaxAmount:
          type: integer
          example: 400
      required: [sku, quantity, lineTaxAmount]

6.7. Visualizing Functional Resources (Mermaid Diagram)

sequenceDiagram
    autonumber
    participant C as Client
    participant API as Tax API
    participant RTS as Tax Rate Service
    participant GEO as Geo Lookup Service

    C->>API: POST /estimate-tax<br/>{ destination, items }
    API->>GEO: Resolve region for postalCode
    GEO-->>API: regionCode = "CA-SF"

    API->>RTS: Calculate rates for regionCode + items
    RTS-->>API: per-item tax amounts

    API-->>C: 200 OK<br/>{ TaxEstimateResult }

This clearly shows:

  • No lifecycle
  • No resource ID
  • A single compute-style command