Files
BanGUI/Docs/Service-Development.md
Lukas 2f9fc8076d refactor(backend): clean up jail service, add error handling service
- Extract jail status/processing to helper functions
- Add error_handling.py service for centralized error handling
- Update config.py with validation and defaults
- Update .env.example with all config options
- Remove obsolete Tasks.md, add Service-Development.md
- Minor fixes across routers and services

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-03 17:40:37 +02:00

3.4 KiB

Service Development Guide

How to write and maintain services in BanGUI.

Error Handling Contracts

Every service method must document which error handling pattern it follows. This lets callers know what to expect without reading the implementation.

The Three Patterns

from app.services.error_handling import ABORT_ON_ERROR, RETURN_DEFAULT, PARTIAL_RESULT

ABORT_ON_ERROR — Raise an exception, let the router convert it to HTTP. Used for: auth, writes, state changes, any operation where partial success is meaningless.

async def start_jail(socket_path: str, name: str) -> None:
    """Start a stopped fail2ban jail.

    Error contract: ABORT_ON_ERROR. Raises JailNotFoundError (404),
    JailOperationError (409), Fail2BanConnectionError (503).
    """
    ...

RETURN_DEFAULT — Return empty result and log warning. Never raises. Used for: informational reads (list, get) where infrastructure unavailability should not block the UI.

async def get_settings(socket_path: str) -> DomainServerSettingsResult:
    """Return current fail2ban server-level settings.

    Error contract: RETURN_DEFAULT. Returns DomainServerSettingsResult
    with default values if socket is unreachable. Never raises.
    """
    ...

PARTIAL_RESULT — Return (result, errors) tuple. Errors collected, not raised. Used for: batch operations on collections where one item failing does not invalidate the rest.

# Not yet used in codebase; define as needed for batch operations.

When to Use Which

Operation type Pattern
Auth / session ABORT_ON_ERROR
Write / state change ABORT_ON_ERROR
Config updates ABORT_ON_ERROR
Single-item read (jail, ban) ABORT_ON_ERROR
Multi-item read (list) RETURN_DEFAULT
Server settings read RETURN_DEFAULT
Batch / parallel fetch PARTIAL_RESULT

Changing Patterns

Switching a method's error contract is a breaking change. Update the docstring, add a changelog entry, and bump the major version if this is a public API.

Service Structure

Services live in backend/app/services/. They contain no HTTP/FastAPI concerns.

app/services/
  ban_service.py       # ban/unban, ban history queries
  jail_service.py      # jail lifecycle, ignore lists
  server_service.py    # server-level settings
  geo_service.py       # geolocation
  ...
  error_handling.py    # contract definitions
  protocols.py         # Protocol interfaces for DI

Protocols

Each service has a corresponding protocol in protocols.py for dependency injection. Protocol methods include the error contract in their docstring:

class JailService(Protocol):
    async def list_jails(self, socket_path: str) -> DomainJailList:
        """Error contract: ABORT_ON_ERROR."""
        ...

Router Error Handling

Routers must not catch and silently swallow exceptions from services using ABORT_ON_ERROR unless they convert to a specific HTTP response. Let domain exceptions propagate — the global exception handlers handle them.

Exception handler registration (in main.py):

  • DomainError → JSON error response
  • Fail2BanConnectionError → HTTP 503
  • JailNotFoundError → HTTP 404

Logging

Log at the service layer using structlog:

log.info("jail_started", jail=name)
log.warning("socket_unreachable_using_default", socket_path=socket_path)

Never log sensitive data (tokens, passwords, IPs in full).