Skip to content

Commit

Permalink
chore: Update README.md with instructions for setting up the developm…
Browse files Browse the repository at this point in the history
…ent environment and running tests
  • Loading branch information
talaman committed Aug 6, 2024
1 parent 0fb78b4 commit d21e9d0
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 12 deletions.
60 changes: 48 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ This repository implements a Pager system using Domain-Driven Design (DDD) and H
- [Ports Layer](#ports-layer)
- [Adapters Layer](#adapters-layer)
- [Getting Started](#getting-started)
- [Running Tests](#running-tests)
- [Your Environment with DevContainers](#your-environment-with-devcontainers)
- [Your Environment Manually](#your-environment-manually)
- [Building and running the Application with Docker](#building-and-running-the-application-with-docker)
- [Continuous Integration](#continuous-integration)
- [Further Improvements](#further-improvements)

## Overview

Expand Down Expand Up @@ -65,34 +68,60 @@ The Adapters Layer implements the interfaces defined in the Ports Layer. It adap

## Getting Started

### Your Environment with DevContainers

I have included a vscode devcontainer configuration to make it easier to get started with the project. If you have vscode, the DevContainers extension and Docker installed, you can open the project in a container and have all the dependencies set up automatically.

Just open the project in vscode and click on the "Reopen in Container" button when prompted.

When the container is ready, you can run the tests using the following command in the terminal:

```sh
pytest
```

### Your Environment Manually

Get your preferred Python environment set up, I have used 3.12 for this project.

Install the dependencies:
```sh
pip install -r requirements.txt
```

When the dependencies are installed, you can run the tests using the following command in the terminal:

```sh
pytest
```

### Building and running the Application with Docker

To get started with the Hexagonal Pager System, follow these steps:

1. **Clone the repository**:
```sh
git clone https://github.com/talaman/hexagonal-pager.git
cd hexagonal-pager
```

2. **Build the Docker image**:
```sh
docker build -t hexagonal-pager .
```

3. **Run the application**:
3. **Test the application**:
```sh
docker run --rm hexagonal-pager pytest
```
4. **Run the application**:
This is an example of how to run the application. But a real application would have an adapter layer to interact with external systems.
```sh
docker run -d hexagonal-pager
```

## Running Tests

To run the tests, use the following command:

```sh
docker run --rm hexagonal-pager pytest
```

## Continuous Integration

[![ci](https://github.com/talaman/hexagonal-pager/actions/workflows/ci.yml/badge.svg)](https://github.com/talaman/hexagonal-pager/actions/workflows/ci.yml)

This project uses GitHub Actions for continuous integration. The CI pipeline is defined in .github/workflows/ci.yml and includes the following steps:

- Checkout code: Uses the actions/checkout@v3 action to checkout the code.
Expand All @@ -103,3 +132,10 @@ This project uses GitHub Actions for continuous integration. The CI pipeline is

The CI pipeline is triggered on every push to the main branch and runs the tests to ensure the code quality and functionality.

## Further Improvements

- Implement the Adapters Layer to integrate with external systems.
- Add more use cases and scenarios to cover additional functionalities.
- Ensure the Pager Service handles concurrency issues, such as preventing multiple notifications to the same target when multiple alerts are received simultaneously.
- Define the expected guarantees from the database regarding consistency and reliability.
- Implement a robust test strategy to cover all edge cases and concurrency scenarios.
63 changes: 63 additions & 0 deletions pager/domain/services/pager_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,29 @@

class PagerService:
def __init__(self, policy_repo: EscalationPolicyRepository, email_sender: EmailSender, sms_sender: SmsSender):
"""
Initializes a PagerService object.
Args:
policy_repo (EscalationPolicyRepository): The repository for escalation policies.
email_sender (EmailSender): The email sender object.
sms_sender (SmsSender): The SMS sender object.
"""
self.policy_repo = policy_repo
self.email_sender = email_sender
self.sms_sender = sms_sender
self.monitored_services: Dict[str, MonitoredService] = {}

def handle_alert(self, alert: Alert):
"""
Handles an alert by marking the corresponding service as unhealthy, notifying the targets, and starting an acknowledgment timer.
Parameters:
- alert (Alert): The alert to handle.
Returns:
- None
"""
service = self.get_or_create_monitored_service(alert.service_id)
if service.state == "Healthy":
service.mark_unhealthy()
Expand All @@ -23,15 +40,43 @@ def handle_alert(self, alert: Alert):
service.start_acknowledgment_timer(15 * 60, lambda: self.handle_timeout(alert.service_id))

def handle_acknowledgement(self, ack: Acknowledgement):
"""
Handles the acknowledgement of an alert.
Parameters:
ack (Acknowledgement): The acknowledgement object containing the service ID.
Returns:
None
"""
service = self.get_or_create_monitored_service(ack.service_id)
if service.state == "Unhealthy":
service.acknowledge_alert()

def handle_healthy_event(self, event: HealthyEvent):
"""
Handles a healthy event by marking the corresponding service as healthy.
Parameters:
event (HealthyEvent): The healthy event to handle.
Returns:
None
"""
service = self.get_or_create_monitored_service(event.service_id)
service.mark_healthy()

def handle_timeout(self, service_id: str):
"""
Handles the timeout event for a specific service.
Args:
service_id (str): The ID of the service.
Raises:
ValueError: If there are no more levels to escalate to.
"""
service = self.monitored_services[service_id]
if service.state == "Unhealthy" and not service.acknowledged:
policy = self.policy_repo.get_policy(service_id)
Expand All @@ -45,13 +90,31 @@ def handle_timeout(self, service_id: str):
pass

def notify_targets(self, targets: List[NotificationTarget]):
"""
Notifies the given list of targets.
Args:
targets (List[NotificationTarget]): A list of NotificationTarget objects.
Returns:
None
"""
for target in targets:
if isinstance(target, EmailTarget):
self.email_sender.send(target.get_contact_info())
elif isinstance(target, SmsTarget):
self.sms_sender.send(target.get_contact_info())

def get_or_create_monitored_service(self, service_id: str) -> MonitoredService:
"""
Retrieves or creates a monitored service based on the given service ID.
Parameters:
service_id (str): The ID of the service.
Returns:
MonitoredService: The retrieved or created monitored service.
"""
if service_id not in self.monitored_services:
self.monitored_services[service_id] = MonitoredService(id=service_id)
return self.monitored_services[service_id]

0 comments on commit d21e9d0

Please sign in to comment.