Skip to content

Observablesยถ

1. What Are Observables?ยถ

Observables are a programming concept used to implement the Observer Pattern, where a function (or object) reacts to events dynamically. This means that:

  • An event is emitted when something significant happens (e.g., a user registers, a payment is processed, etc.).
  • Subscribers (listeners) react to that event without direct coupling to the emitter.
  • This allows for a highly flexible, decoupled, and scalable event-driven architecture.

Observables can be thought of as a stream of events, much like how promises handle asynchronous data but continuously over time. Instead of handling a single future result, observables enable reactive programming, where parts of the system automatically respond to changes.

2. Benefits of Using Observablesยถ

Using observables in an application, especially within Esmerald, comes with several advantages:

โœ… Decoupling & Maintainabilityยถ

Observables help separate event producers (emitters) from event consumers (listeners). This reduces dependencies and makes the system easier to maintain.

โœ… Scalability & Extensibilityยถ

By using observables, new features can be added without modifying existing code. Developers can subscribe to events dynamically instead of changing core logic.

โœ… Concurrency & Efficiencyยถ

Using async-based event dispatching, Esmerald handles multiple listeners without blocking execution. This improves performance in real-time applications.

โœ… Code Reusabilityยถ

Once an observable event is defined, it can be reused across multiple parts of the application, reducing redundant logic.

โœ… Better Error Handlingยถ

Observables allow for centralized error handling on emitted events, making debugging more manageable.

3. Why Should You Use Observables?ยถ

Observables are useful in scenarios where something happens and multiple parts of the system need to react independently.

For example:

  • User Registration Events โ†’ Send a welcome email, log activity, and assign default roles.
  • Payment Processing โ†’ Notify the user, update the database, and trigger order fulfillment.
  • Live Data Streaming โ†’ Real-time notifications, stock updates, or WebSocket messages.
  • Background Tasks โ†’ Perform long-running operations (e.g., data processing, cleanup).
  • Logging & Monitoring โ†’ Collect application metrics without affecting request performance.

In Esmerald, observables allow for an efficient and scalable event-driven approach, making it ideal for high-performance applications.


4. How Observables Are Applied in Esmeraldยถ

Esmerald provides built-in support for observables through the @observable decorator and EventDispatcher.

๐Ÿ”น Key Componentsยถ

  1. @observable Decorator
  2. Defines functions that can emit and/or listen for events.
  3. EventDispatcher
  4. Manages event subscriptions and emissions asynchronously.
  5. Async and Sync Support
  6. Supports both asynchronous and synchronous event handlers.
  7. Concurrency Handling
  8. Uses anyio.create_task_group() to handle multiple listeners in parallel.

5. Real-World Examples Using Esmeraldยถ

Example 1: User Registration with Multiple Side Effectsยถ

A user registers, and multiple actions occur without coupling the logic together.

from esmerald import post
from esmerald.utils.decorators import observable


# User registration endpoint
@post("/register")
@observable(send=["user_registered"])
async def register_user(data: dict):
    return {"message": "User registered successfully!"}


# Listeners for the event
@observable(listen=["user_registered"])
async def send_welcome_email():
    print("Sending welcome email...")


@observable(listen=["user_registered"])
async def assign_default_roles():
    print("Assigning default roles to the user...")
What Happens Here? โœ… A user registers โ†’ Event "user_registered" is emitted. โœ… The system automatically triggers listeners โ†’ Email is sent, roles are assigned. โœ… No direct dependency โ†’ Can easily add more listeners in the future.


Example 2: Payment Processingยถ

When a payment is made, multiple systems react to the event.

from esmerald import post
from esmerald.utils.decorators import observable


@post("/pay")
@observable(send=["payment_success"])
async def process_payment():
    return {"message": "Payment processed!"}


@observable(listen=["payment_success"])
async def notify_user():
    print("Notifying user about payment confirmation...")


@observable(listen=["payment_success"])
async def update_database():
    print("Updating payment database records...")


@observable(listen=["payment_success"])
async def generate_invoice():
    print("Generating invoice for the payment...")

โœ… One event triggers multiple independent processes. โœ… Fully decoupled logic for better maintainability.


Example 3: Logging User Activityยถ

from esmerald import post
from esmerald.utils.decorators import observable


@post("/login")
@observable(send=["user_logged_in"])
async def login():
    return {"message": "User logged in!"}


@observable(listen=["user_logged_in"])
async def log_login_activity():
    print("Logging user login activity...")

โœ… Logs login activity without modifying authentication logic.


Example 4: Real-Time Notificationsยถ

from esmerald import post
from esmerald.utils.decorators import observable


@post("/comment")
@observable(send=["new_comment"])
async def add_comment():
    return {"message": "Comment added!"}


@observable(listen=["new_comment"])
async def send_notification():
    print("Sending notification about the new comment...")

โœ… Users get notified immediately after a comment is posted.


Example 5: Background Data Processingยถ

from esmerald import post
from esmerald.utils.decorators import observable


@post("/upload")
@observable(send=["file_uploaded"])
async def upload_file():
    return {"message": "File uploaded successfully!"}


@observable(listen=["file_uploaded"])
async def process_file():
    print("Processing file in the background...")

โœ… Heavy file processing runs asynchronously, without blocking the request.


Example 6: Scheduled Tasks & Cleanup Jobsยถ

from esmerald.utils.decorators import observable


@observable(send=["daily_cleanup"])
async def trigger_cleanup():
    print("Daily cleanup event triggered!")


@observable(listen=["daily_cleanup"])
async def delete_old_records():
    print("Deleting old database records...")


@observable(listen=["daily_cleanup"])
async def clear_cache():
    print("Clearing application cache...")

โœ… Scheduled task runs automatically โ†’ Triggers multiple cleanup tasks.


Conclusionยถ

Observables in Esmerald allow developers to build efficient, scalable, and maintainable event-driven applications. By leveraging @observable and EventDispatcher:

โœ”๏ธ Events are decoupled from logic, improving maintainability. โœ”๏ธ Asynchronous execution improves performance. โœ”๏ธ Easily extend functionality without modifying existing code. โœ”๏ธ Ensures a clean, modular architecture.

Whether you're handling user events, background jobs, notifications, or real-time updates, observables empower you to build dynamic and reactive applications. ๐Ÿš€