Design Patterns

Notes and exercises for learning design patterns

View the Project on GitHub Claptar/design-patterns

Facade Exercise 2: Multiple Report Types and Error Handling

Goal

Practice two things that arise naturally as a Facade grows:

  1. The Facade exposes multiple operations with different orchestration paths.
  2. The Facade owns error handling and cleanup so callers never see half-completed operations.

Scenario

The reporting tool from Exercise 1 has grown. The company now needs two kinds of reports: the existing weekly sales report and a new monthly executive summary.

The monthly summary has a different flow:

  1. Fetch sales data for the full month (data_fetcher.fetch_monthly_sales()).
  2. Fetch the KPI snapshot (kpi_fetcher.fetch_kpis()).
  3. Format both into a combined summary (formatter.format_executive(data, kpis)).
  4. Save the report to a file archive before emailing (archiver.save(report, filename)).
  5. Email it to the board (emailer.send(report, recipients)).

There is also a new error requirement: if the archiver fails (raises ArchiveError), the email must not be sent. The caller should receive a clean ReportDeliveryError instead of a raw ArchiveError. The caller should never have to handle subsystem-specific exceptions.


What you need to build

Extend ReportFacade with a second method:

facade.send_monthly_executive_summary(
    recipients=["board@example.com"],
    archive_filename="executive_2024_10.txt",
)

And wrap errors so that:

try:
    facade.send_monthly_executive_summary(
        recipients=["board@example.com"],
        archive_filename="executive_2024_10.txt",
    )
except ReportDeliveryError as e:
    print(f"Delivery failed: {e}")

Never leaks ArchiveError to the caller.


New subsystem classes provided

The exercise file adds:

The existing SalesDataFetcher, ReportFormatter, and ReportEmailer are extended with the new methods needed.


Constraints


File to edit

exercise2.py

Run the tests with:

python -m pytest exercise2.py -v