Skip to content

Commit df14144

Browse files
leandrodamascenayaythomas
authored andcommitted
docs: add best practices docs
1 parent 7526376 commit df14144

File tree

1 file changed

+121
-1
lines changed

1 file changed

+121
-1
lines changed

docs/best-practices.md

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- [Timeout configuration](#timeout-configuration)
88
- [Naming conventions](#naming-conventions)
99
- [Performance optimization](#performance-optimization)
10+
- [Serialization](#serialization)
1011
- [Common mistakes](#common-mistakes)
1112
- [Code organization](#code-organization)
1213
- [FAQ](#faq)
@@ -500,6 +501,80 @@ def lambda_handler(event: dict, context: DurableContext) -> dict:
500501

501502
[↑ Back to top](#table-of-contents)
502503

504+
## Serialization
505+
506+
### Use JSON-serializable types
507+
508+
The SDK uses JSON serialization by default for checkpoints. Stick to JSON-compatible types (dict, list, str, int, float, bool, None) for operation inputs and results.
509+
510+
**Good:**
511+
512+
```python
513+
@durable_step
514+
def process_order(step_context: StepContext, order: dict) -> dict:
515+
return {
516+
"order_id": order["id"],
517+
"total": 99.99,
518+
"items": ["item1", "item2"],
519+
"processed": True,
520+
}
521+
```
522+
523+
**Avoid:**
524+
525+
```python
526+
from datetime import datetime
527+
from decimal import Decimal
528+
529+
@durable_step
530+
def process_order(step_context: StepContext, order: dict) -> dict:
531+
# datetime and Decimal aren't JSON-serializable by default
532+
return {
533+
"order_id": order["id"],
534+
"total": Decimal("99.99"), # Won't serialize!
535+
"timestamp": datetime.now(), # Won't serialize!
536+
}
537+
```
538+
539+
### Convert non-serializable types
540+
541+
Convert complex types to JSON-compatible formats before returning from steps:
542+
543+
```python
544+
from datetime import datetime
545+
from decimal import Decimal
546+
547+
@durable_step
548+
def process_order(step_context: StepContext, order: dict) -> dict:
549+
return {
550+
"order_id": order["id"],
551+
"total": float(Decimal("99.99")), # Convert to float
552+
"timestamp": datetime.now().isoformat(), # Convert to string
553+
}
554+
```
555+
556+
### Use custom serialization for complex types
557+
558+
For complex objects, implement custom serialization or use the SDK's SerDes system:
559+
560+
```python
561+
from dataclasses import dataclass, asdict
562+
563+
@dataclass
564+
class Order:
565+
order_id: str
566+
total: float
567+
items: list
568+
569+
@durable_step
570+
def process_order(step_context: StepContext, order_data: dict) -> dict:
571+
order = Order(**order_data)
572+
# Process order...
573+
return asdict(order) # Convert dataclass to dict
574+
```
575+
576+
[↑ Back to top](#table-of-contents)
577+
503578
## Common mistakes
504579

505580
### ⚠️ Modifying mutable objects between steps
@@ -526,6 +601,51 @@ def lambda_handler(event: dict, context: DurableContext) -> dict:
526601
return data
527602
```
528603

604+
### ⚠️ Using context inside its own operations
605+
606+
**Wrong:**
607+
608+
```python
609+
@durable_step
610+
def process_with_wait(step_context: StepContext, context: DurableContext) -> str:
611+
# DON'T: Can't use context inside its own step operation
612+
context.wait(seconds=1) # Error: using context inside step!
613+
result = context.step(nested_step(), name="step2") # Error: nested context.step!
614+
return result
615+
616+
@durable_execution
617+
def lambda_handler(event: dict, context: DurableContext) -> dict:
618+
# This will fail - context is being used inside its own step
619+
result = context.step(process_with_wait(context), name="step1")
620+
return {"result": result}
621+
```
622+
623+
**Right:**
624+
625+
```python
626+
@durable_step
627+
def nested_step(step_context: StepContext) -> str:
628+
return "nested step"
629+
630+
@durable_with_child_context
631+
def process_with_wait(child_ctx: DurableContext) -> str:
632+
# Use child context for nested operations
633+
child_ctx.wait(seconds=1)
634+
result = child_ctx.step(nested_step(), name="step2")
635+
return result
636+
637+
@durable_execution
638+
def lambda_handler(event: dict, context: DurableContext) -> dict:
639+
# Use run_in_child_context for nested operations
640+
result = context.run_in_child_context(
641+
process_with_wait(),
642+
name="block1"
643+
)
644+
return {"result": result}
645+
```
646+
647+
**Why:** You can't use a context object inside its own operations (like calling `context.step()` inside another `context.step()`). Use child contexts to create isolated execution scopes for nested operations.
648+
529649
### ⚠️ Forgetting to handle callback timeouts
530650

531651
**Wrong:**
@@ -685,7 +805,7 @@ def lambda_handler(event: dict, context: DurableContext) -> dict:
685805

686806
**Q: How many steps should a durable function have?**
687807

688-
A: There's no hard limit, but keep in mind that more steps mean more API operations and longer execution time. Balance granularity with performance - group related operations when it makes sense, but don't hesitate to break down complex logic into steps.
808+
A: There's a limit of 3,000 operations per execution. Keep in mind that more steps mean more API operations and longer execution time. Balance granularity with performance - group related operations when it makes sense, but don't hesitate to break down complex logic into steps.
689809

690810
**Q: Should I create a step for every function call?**
691811

0 commit comments

Comments
 (0)