Skip to content

Conversation

@prosdev
Copy link
Contributor

@prosdev prosdev commented Jan 10, 2026

Summary

Implements persistent storage for events and errors using Google Cloud Firestore with batch operations, retry logic, and stream-based isolation.

Closes #2

Implementation Details

FirestoreEventStore

  • Storage structure: Subcollections per stream ()
  • Batching: Automatic chunking into 500-doc batches (Firestore limit)
  • Retry logic: Exponential backoff for transient errors (DeadlineExceeded, ServiceUnavailable, InternalServerError)
  • Async wrapper: Uses asyncio.to_thread() to wrap sync Firestore client
  • Methods: store(), store_batch(), read(stream, limit)

FirestoreErrorStore (Dead Letter Queue)

  • Storage structure: Flat errors/ collection
  • Captures: Raw payload, error message, timestamp, metadata
  • Methods: store_error(), query_errors(limit)

Testing

  • Unit tests: 20 tests with mocked Firestore client (100% coverage)
  • Integration tests: 3 tests with Docker Compose Firestore emulator
    • End-to-end write/read (100 events)
    • Stream isolation (mobile vs web)
    • Error store persistence

Docker Compose

  • Added docker-compose.yml with Firestore emulator
  • Tests skip gracefully when emulator not running
  • Updated README with Docker usage instructions

Commits

  1. Setup + serialization helpers - FirestoreEventStore base, path construction, serialization
  2. Write path + retry logic - store(), store_batch() with tenacity retries
  3. Read path + deserialization - read(), _dict_to_event() with type routing
  4. Error store (DLQ) - FirestoreErrorStore for failed events
  5. Integration tests + Docker - Docker Compose + 3 integration tests
  6. Documentation - Updated tasks.md acceptance criteria

Testing

# Unit tests (fast, no Docker)
uv run pytest

# Integration tests (requires Docker)
docker compose up -d
export FIRESTORE_EMULATOR_HOST=localhost:8080
uv run pytest -m integration
docker compose down

Files Changed

  • src/eventkit/stores/firestore.py (new, 240 lines)
  • tests/unit/stores/test_firestore.py (new, 270 lines)
  • tests/integration/test_firestore_integration.py (new, 140 lines)
  • docker-compose.yml (new)
  • pytest.ini (updated with integration marker)
  • README.md (updated with Docker instructions)
  • pyproject.toml (added tenacity dependency)
  • src/eventkit/schema/events.py (added stream field)
  • specs/core-pipeline/tasks.md (marked Task 4 complete)

Dependencies Added

  • tenacity>=8.2.0 - Retry logic with exponential backoff

Acceptance Criteria (All Met)

  • FirestoreEventStore implements EventStore Protocol
  • Batch writes (max 500 docs per batch)
  • Document ID: {stream}/{timestamp}_{uuid}
  • Retry logic for transient errors
  • FirestoreErrorStore implements ErrorStore Protocol
  • Unit tests with mocked Firestore client
  • Integration tests with Firestore emulator

- Add tenacity dependency for retry logic
- Create FirestoreEventStore class with Firestore client init
- Implement _get_doc_ref() for events/{stream}/items/{id} path construction
- Implement _event_to_dict() for datetime serialization
- Add stream field to TypedEvent for routing support
- Add comprehensive unit tests for setup and helpers
- All tests passing with 100% coverage on new code
- Implement store() for single event writes with asyncio.to_thread wrapper
- Implement store_batch() with automatic 500-event chunking
- Add _write_with_retry() and _write_batch_with_retry() with tenacity
- Exponential backoff on DeadlineExceeded, ServiceUnavailable, InternalServerError
- Add comprehensive unit tests for write operations and retry behavior
- Test batch chunking (501 events → 2 batches)
- Test retry success after 2 failures
- Test retry exhaustion raises RetryError after 3 attempts
- All tests passing with 100% coverage on new code
- Implement read(stream, limit) for querying events by stream
- Query returns events in reverse chronological order (newest first)
- Add _dict_to_event() for deserializing Firestore docs to TypedEvent
- Handle IdentifyEvent, TrackEvent, PageEvent deserialization
- Convert ISO timestamp strings back to datetime objects
- Add retry logic to read operations
- Add comprehensive unit tests for read operations
- Test empty stream returns empty list
- Test deserialization for all event types
- Test ValueError on unknown event_type
- All tests passing with 100% coverage on new code
- Create FirestoreErrorStore class for failed event storage
- Implement store_error() for capturing payload, error, timestamp, metadata
- Implement query_errors() for retrieving errors in reverse chronological order
- Use flat errors/ collection structure for simple querying
- Add retry logic to error storage operations
- Add comprehensive unit tests for error store
- Test error storage with and without metadata
- Test error querying and empty results
- All 20 unit tests passing with 100% coverage on new code
- Add docker-compose.yml with Firestore emulator
- Add 3 integration tests:
  - test_write_and_read_events: Write 100 events, read back, verify
  - test_stream_isolation: Verify mobile/web streams are isolated
  - test_error_store_persistence: Verify DLQ stores and retrieves errors
- Add integration marker to pytest.ini
- Tests skip gracefully when FIRESTORE_EMULATOR_HOST not set
- Update README with Docker Compose usage instructions
- Add asyncio_mode to pytest.ini for async test support
- All integration tests pass when emulator is running
- Update specs/core-pipeline/tasks.md acceptance criteria
- All 7 acceptance criteria for Task 4 now marked complete [x]
- FirestoreEventStore and FirestoreErrorStore fully implemented
- 20 unit tests passing with 100% coverage
- 3 integration tests passing with Docker Compose
- Comprehensive docstrings on all public methods
@prosdev prosdev force-pushed the feat/firestore-storage branch from de58d55 to 17cafd1 Compare January 10, 2026 18:51
- Add return type annotations to _read_with_retry() and _query_errors_with_retry()
- Use Any for Firestore generator return types
- Add mypy to pre-commit hooks
- All type checks now pass
@prosdev prosdev merged commit e7489a2 into main Jan 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Firestore Storage

2 participants