CasaOS Service Layer Architecture: How Services Communicate in the Repository Pattern

CasaOS implements a repository-style service layer where a global singleton service.MyService exposes all domain services via the Repository interface, enabling direct Go method calls for synchronous operations and the CasaOS Message Bus for asynchronous event distribution.

The CasaOS service layer architecture follows a clean, modular design that separates business logic from transport concerns. At its core lies a singleton repository pattern that aggregates all services—such as system management, notifications, and peer discovery—into a single access point. This architecture supports multiple communication pathways, from simple in-process method calls to distributed event broadcasting, making it straightforward for HTTP handlers and background jobs to interact with domain logic.

Core Service Layer Design

The Repository Interface

The foundation of the service layer resides in service/service.go, which defines the Repository interface and constructs concrete service implementations. This interface acts as a service locator, exposing methods like Casa(), Notify(), System(), and Gateway() to retrieve specific service instances.

type Repository interface {
    Casa() CasaService
    Notify() NotifyServer
    System() SystemService
    Gateway() external.ManagementService
    MessageBus() *codegen.ClientWithResponses
    // ... additional service accessors
}

func NewService(db *gorm.DB, RuntimePath string) Repository {
    return &store{
        casa:   NewCasaService(),
        notify: NewNotifyService(db),
        system: NewSystemService(),
        // ... other initializations
    }
}

The repository is instantiated once during application bootstrap in main/main.go:

service.MyService = service.NewService(sqliteDB, config.CommonInfo.RuntimePath)
service.Cache = cache.Init()

Concrete Service Implementations

Each domain service lives in its own file under the service/ directory and implements a thin interface:

  • service/system.go: Implements SystemService for hardware introspection and system control (e.g., GetCpuPercent(), reboot/shutdown operations).
  • service/notify.go: Implements NotifyServer for managing notification logs and publishing events to the Message Bus via SendNotify().
  • service/casa.go: Implements CasaService for remote version checking with built-in caching via go-cache.
  • service/connections.go: Implements ConnectionsService for DB-driven CIFS mount management.

Service Communication Patterns

Direct Method Calls via Singleton

The primary communication mechanism involves direct Go method calls through the global service.MyService variable. HTTP handlers and background tasks obtain service references and invoke methods synchronously:

// Example from route/v1/system.go
need, version := version.IsNeedUpdate(service.MyService.Casa().GetCasaosVersion())
cpuPercent := service.MyService.System().GetCpuPercent()

This pattern provides type-safe, compile-time checked communication with minimal overhead.

External Gateway Integration

For operations requiring interaction with the CasaOS-Common management component, the repository exposes Gateway(), which returns an external.ManagementService. This service handles route creation, port configuration, and other system-level networking tasks:

response, err := service.MyService.Gateway().CreateRoute(&model.Route{
    Path:   "/v2/app",
    Target: "localhost:8080",
})

Asynchronous Event Distribution

For decoupled, asynchronous communication, services utilize the CasaOS Message Bus. The NotifyServer publishes events through a generated OpenAPI client (codegen/message_bus/api.go), allowing cross-service and cross-process event distribution:

func (i *notifyServer) SendNotify(name string, message map[string]interface{}) {
    response, err := MyService.MessageBus().PublishEventWithResponse(
        context.Background(),
        common.SERVICENAME,
        name,
        message,
    )
    // ... handle response
}

Simultaneously, the notify service maintains active WebSocket connections (WebSocketConns) to push real-time notifications to connected clients.

Shared In-Memory Caching

Services share a global cache instance (service.Cache) backed by go-cache. This enables cheap, in-process data sharing for frequently accessed, ephemeral data such as thermal zone paths and remote version information:

// In service/casa.go
Cache.Set(keyName, version, time.Minute*20)

Practical Implementation Example

The following example demonstrates initializing the service layer and utilizing multiple communication patterns:

package main

import (
    "github.com/IceWhaleTech/CasaOS/service"
    "github.com/IceWhaleTech/CasaOS/pkg/config"
)

func main() {
    // Initialize the global repository (normally performed in main.go)
    db := /* gorm DB */ nil
    service.MyService = service.NewService(db, config.CommonInfo.RuntimePath)

    // Direct method call: Fetch CPU utilization
    cpuPct := service.MyService.System().GetCpuPercent()
    println("Current CPU %:", cpuPct)

    // Asynchronous event: Broadcast notification via Message Bus
    msg := map[string]interface{}{
        "title": "System Status",
        "body":  "CasaOS is operational",
    }
    service.MyService.Notify().SendNotify("casaos:info", msg)
}

This code performs a synchronous hardware query through SystemService and publishes an asynchronous event through NotifyServer, illustrating the dual communication models available within the architecture.

Key Source Files to Explore

Understanding the CasaOS service layer architecture requires examining these critical files:

Summary

  • Repository Pattern: CasaOS uses a singleton Repository interface (service.MyService) to aggregate all domain services, providing a unified access point throughout the application.
  • Synchronous Communication: Direct Go method calls via the singleton offer type-safe, high-performance interaction between HTTP handlers and business logic.
  • Asynchronous Messaging: The Message Bus client enables decoupled event distribution across services and processes, while WebSocket connections provide real-time client notifications.
  • External Integration: The Gateway service abstracts communication with the CasaOS-Common management component for network and routing operations.
  • Shared Resources: A global go-cache instance (service.Cache) provides in-process caching accessible to all services.

Frequently Asked Questions

What architectural pattern does CasaOS use for its service layer?

CasaOS implements a repository-style service layer combined with the singleton pattern. The Repository interface defined in service/service.go acts as a service locator, exposing accessor methods for all concrete services. A global variable service.MyService holds the single instance initialized at startup, ensuring consistent state and resource sharing across the application.

How do HTTP handlers access business logic in CasaOS?

HTTP handlers access business logic by importing the service package and invoking methods through the global service.MyService singleton. For example, a handler might call service.MyService.System().GetCpuPercent() to retrieve hardware statistics or service.MyService.Casa().GetCasaosVersion() to check for updates. This approach keeps handlers thin and delegates all domain logic to the service layer.

What is the role of the Message Bus in CasaOS architecture?

The Message Bus provides asynchronous, decoupled communication between services and external components. Implemented via a generated OpenAPI client in codegen/message_bus/api.go, it allows services like NotifyServer to publish events using PublishEventWithResponse(). This enables cross-service notifications without direct method coupling, supporting event-driven workflows and integration with external CasaOS components.

How does CasaOS deliver real-time notifications to web clients?

Real-time notifications flow through two pathways: first, the NotifyServer publishes events to the Message Bus for system-wide distribution; second, it maintains a slice of active WebSocket connections (WebSocketConns) and broadcasts messages directly to connected clients. This dual approach ensures both internal service awareness and immediate user interface updates.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →