Skip to content

Architecture

Akram El Assas edited this page Aug 18, 2025 · 24 revisions

Table of Contents

Overview

Servy is a Windows application that lets you run any executable as a Windows service through a simple CLI or GUI interface. It provides a reliable, fully managed solution to automate app startup, monitor running processes, and ensure background execution on Windows platforms ranging from Windows 7 SP1 up to Windows 11 and Windows Server editions.

Designed as an open-source alternative to NSSM, Servy is built entirely in C# for simplicity, transparency, and seamless integration into your workflows and automation pipelines.

Project Structure

The Servy solution consists of six main projects:

Project Type Description
Servy WPF Application Main user interface for service configuration and management
Servy.CLI Console Application Main CLI for service configuration and management
Servy.Core Class Library Shared functionality, utilities, and data models
Servy.Infrastructure Class Library Data access, persistence, and integration with external systems (e.g., SQLite database, configuration files)
Servy.Service Windows Service Windows Service executable that wraps target processes
Servy.Restarter Console Application Service restart utility

Architecture Layers

Servy follows Clean Architecture principles, separating responsibilities into distinct tiers for clarity, maintainability, and testability:

┌───────────────────────────────────┐
│                                   │
│        Presentation Layer         │
│                                   │
│    (Servy Apps - GUI & CLI)       │
│ Handles user interaction, input,  │
│ and output. Communicates with the │
│ business logic layer.             │
│                                   │
├───────────────────────────────────┤
│                                   │
│        Business Logic Layer       │
│                                   │
│            (Servy.Core)           │
│ Implements the core functionality,│
│ workflows, and service management │
│ rules, independent of UI.         │
│                                   │
├───────────────────────────────────┤
│                                   │
│      Infrastructure Layer         │
│                                   │
│     (Servy.Infrastructure)        │
│ Provides data persistence, access │
│ to the SQLite database, config    │
│ management, and external system   │
│ integration.                      │
│                                   │
├───────────────────────────────────┤
│                                   │
│          Service Layer            │
│                                   │
│          (Servy.Service)          │
│ A Windows Service host that runs  │
│ configured apps in the background,│
│ monitors them, and applies health │
│ checks and restart policies.      │
│                                   │
└───────────────────────────────────┘

The diagram below represents Servy’s layered architecture following Clean Architecture principles. At the center is the Core layer (Servy.Core), which contains the domain entities (Service) and abstractions/interfaces (IServiceRepository, IServiceManager). This layer is independent of external dependencies, ensuring business logic remains decoupled and testable.

The Infrastructure layer (Servy.Infrastructure) implements the Core interfaces, providing concrete functionality like database persistence (ServiceRepository), XML/JSON serialization, and password encryption. External services and APIs are only referenced here, not in the Core, following the dependency inversion principle.

The Application or Orchestrator layer (for example, ServiceManager) coordinates domain operations, calling repositories and services, without containing business rules itself. In Clean Architecture terms, dependencies point inwards toward the Core, ensuring the inner domain remains stable even as infrastructure changes.

This separation allows for flexible testing, easier maintenance, and adaptability, as the domain logic does not rely on concrete implementations or frameworks.

flowchart TB
    subgraph Core ["Servy.Core"]
        A[Service Domain Model] 
        B[IServiceRepository Interface]
        C[IServiceManager Interface]
    end

    subgraph Infrastructure ["Servy.Infrastructure"]
        D[ServiceRepository Implementation]
        E[Xml/Json Serialization Services]
        F[SecurePassword Service]
    end

    subgraph Application ["Application / Orchestrator"]
        G[ServiceManager]
    end

    %% Dependencies
    D --> B
    E --> B
    F --> B
    G --> A
    G --> B
    G --> D
Loading

Project Details

Servy (Main Application)

The main WPF application provides the user interface for creating and managing Windows services. The application is built using the MVVM (Model-View-ViewModel) design pattern to ensure clean separation of concerns and maintainable code architecture.

Key Responsibilities:

  • Provide user-friendly WPF interface for service configuration
  • Handle user input validation
  • Communicate with Windows Service Control Manager
  • Manage service installation, uninstallation, and configuration
  • Handle UAC elevation requests

Key Features:

  • Service name & description configuration
  • Startup type selection (Automatic, Manual, Disabled)
  • Process priority settings (Idle to Real Time)
  • Custom working directory and parameters
  • Output redirection with log rotation
  • Health checks and automatic service recovery
  • Environment variables
  • Service dependencies
  • Monitor and manage services in real-time
  • Admin privilege management

Class Diagram - Servy (Main Application)

classDiagram
  direction LR

  class App {
    +OnStartup(e: StartupEventArgs)
    -KillServyServiceIfRunning()
    -CopyEmbeddedResource(fileName: string)
    -GetEmbeddedResourceLastWriteTime(assembly: Assembly): DateTime
  }

  class MainWindow {
    +MainWindow()
  }

  class ServiceConfiguration {
    +Name: string
    +Description: string
    +ExecutablePath: string
    +StartupDirectory: string
    +Parameters: string
    +StartupType: ServiceStartType
    +Priority: ProcessPriority
    +StdoutPath: string
    +StderrPath: string
    +EnableRotation: bool
    +RotationSize: string
    +EnableHealthMonitoring: bool
    +HeartbeatInterval: string
    +MaxFailedChecks: string
    +RecoveryAction: RecoveryAction
    +MaxRestartAttempts: string
  }

  class IFileDialogService {
    <<interface>>
    +OpenExecutable(): string
    +OpenFolder(): string
    +SaveFile(title: string): string
  }

  class DesignTimeFileDialogService {
    +OpenExecutable(): string
    +OpenFolder(): string
    +SaveFile(title: string): string
  }

  class FileDialogService {
    +OpenExecutable(): string
    +OpenFolder(): string
    +SaveFile(title: string): string
  }

  class IMessageBoxService {
    <<interface>>
    +ShowInfo(message: string, caption: string)
    +ShowWarning(message: string, caption: string)
    +ShowError(message: string, caption: string)
  }

  class MessageBoxService {
    +ShowInfo(message: string, caption: string)
    +ShowWarning(message: string, caption: string)
    +ShowError(message: string, caption: string)
  }

  class IServiceCommands {
    <<interface>>
    +InstallService(...)
    +UninstallService(serviceName: string)
    +StartService(serviceName: string)
    +StopService(serviceName: string)
    +RestartService(serviceName: string)
  }

  class ServiceCommands {
    -IServiceManager _serviceManager
    -IMessageBoxService _messageBoxService
    +ServiceCommands(serviceManager: IServiceManager, messageBoxService: IMessageBoxService)
    +InstallService(...)
    +UninstallService(serviceName: string)
    +StartService(serviceName: string)
    +StopService(serviceName: string)
    +RestartService(serviceName: string)
  }

  class MainViewModel {
    +MainViewModel(fileDialogService: IFileDialogService, serviceCommands: IServiceCommands)
  }

  App ..> Servy.Service : uses
  MainWindow ..> MainViewModel : uses
  MainWindow ..> FileDialogService : uses
  MainWindow ..> ServiceCommands : uses
  MainWindow ..> Servy.Core.Services.ServiceManager : uses
  MainWindow ..> Servy.Core.Services.ServiceControllerWrapper : uses
  MainWindow ..> Servy.Core.Services.WindowsServiceApi : uses
  MainWindow ..> Servy.Core.Services.Win32ErrorProvider : uses

  ServiceConfiguration ..> Servy.Core.Enums.ServiceStartType : uses
  ServiceConfiguration ..> Servy.Core.Enums.ProcessPriority : uses
  ServiceConfiguration ..> Servy.Core.Enums.RecoveryAction : uses

  IFileDialogService <|-- DesignTimeFileDialogService
  IFileDialogService <|-- FileDialogService
  IMessageBoxService <|-- MessageBoxService
  IServiceCommands <|-- ServiceCommands

  ServiceCommands ..> Servy.Core.Interfaces.IServiceManager : uses
  ServiceCommands ..> IMessageBoxService : uses
  ServiceCommands ..> Servy.Core.Helpers.Helper : uses
  ServiceCommands ..> Servy.Core.Enums.ServiceStartType : uses
  ServiceCommands ..> Servy.Core.Enums.ProcessPriority : uses
  ServiceCommands ..> Servy.Core.Enums.RecoveryAction : uses

  MainViewModel ..> IFileDialogService : uses
  MainViewModel ..> IServiceCommands : uses
Loading

Servy CLI (CLI)

The Servy CLI provides a text-based interface for advanced users and automation scenarios to create, configure, and manage Windows services. It complements the main WPF application by enabling scripting, CI/CD integration, and headless usage.

Key Responsibilities:

  • Configure Windows services via command line parameters and scripts
  • Install, uninstall, start, stop, and query services without UI
  • Support all service settings (name, description, startup type, priority, working directory, parameters)
  • Manage output redirection and log rotation programmatically
  • Request UAC elevation when required
  • Return meaningful exit codes for scripting automation and error handling

Key Features:

  • Full service lifecycle management (install, uninstall, start, stop)
  • Service configuration (name, description, startup type: automatic/manual/disabled)
  • Process priority adjustment (Idle to Real Time)
  • Custom working directory and command-line parameters
  • Stdout/stderr redirection with log rotation options
  • Health monitoring and automatic service recovery triggers
  • Environment variables
  • Service dependencies
  • Designed for scripting, CI/CD pipelines, and remote management
  • Admin privilege detection and elevation support

Note:
The CLI is designed as a lightweight, script-friendly alternative to the WPF interface, focusing on automation and headless scenarios while sharing core service management logic with the GUI application.

Class Diagram - Servy.CLI (CLI)

classDiagram
    class Program {
        +static Main(string[] args)
    }
    class BaseCommand {
        #ExecuteWithHandling(Func<CommandResult> action)
        #ExecuteWithHandlingAsync(Func<Task<CommandResult>> action)
    }
    class InstallServiceCommand {
        -IServiceManager _serviceManager
        -IServiceInstallValidator _validator
        +InstallServiceCommand(IServiceManager serviceManager, IServiceInstallValidator validator)
        +Execute(InstallServiceOptions opts)
    }
    class StartServiceCommand {
        -IServiceManager _serviceManager
        +StartServiceCommand(IServiceManager serviceManager)
        +Execute(StartServiceOptions opts)
    }
    class StopServiceCommand {
        -IServiceManager _serviceManager
        +StopServiceCommand(IServiceManager serviceManager)
        +Execute(StopServiceOptions opts)
    }
    class RestartServiceCommand {
        -IServiceManager _serviceManager
        +RestartServiceCommand(IServiceManager serviceManager)
        +Execute(RestartServiceOptions opts)
    }
    class UninstallServiceCommand {
        -IServiceManager _serviceManager
        +UninstallServiceCommand(IServiceManager serviceManager)
        +Execute(UninstallServiceOptions opts)
    }
    class CommandResult {
        +bool Success
        +string Message
        +static Ok(string message)
        +static Fail(string message)
    }
    class ServiceInstallValidator {
        +Validate(InstallServiceOptions opts)
    }
    class IServiceInstallValidator {
        <<interface>>
        +Validate(InstallServiceOptions opts)
    }
    class IServiceManager {
        <<interface>>
        +InstallService(...)
        +StartService(string serviceName)
        +StopService(string serviceName)
        +RestartService(string serviceName)
        +UninstallService(string serviceName)
    }
    class InstallServiceOptions {
        +string ServiceName
        +string ServiceDescription
        +string ProcessPath
        +string StartupDirectory
        +string ProcessParameters
        +string ServiceStartType
        +string ProcessPriority
        +string StdoutPath
        +string StderrPath
        +string RotationSize
        +string HeartbeatInterval
        +string MaxFailedChecks
        +string RecoveryAction
        +string MaxRestartAttempts
    }
    class StartServiceOptions {
        +string ServiceName
    }
    class StopServiceOptions {
        +string ServiceName
    }
    class RestartServiceOptions {
        +string ServiceName
    }
    class UninstallServiceOptions {
        +string ServiceName
    }

    Program --> InstallServiceCommand
    Program --> StartServiceCommand
    Program --> StopServiceCommand
    Program --> RestartServiceCommand
    Program --> UninstallServiceCommand
    Program --> IServiceInstallValidator
    Program --> IServiceManager

    InstallServiceCommand --|> BaseCommand
    StartServiceCommand --|> BaseCommand
    StopServiceCommand --|> BaseCommand
    RestartServiceCommand --|> BaseCommand
    UninstallServiceCommand --|> BaseCommand

    ServiceInstallValidator --|> IServiceInstallValidator

    InstallServiceCommand --> IServiceManager
    InstallServiceCommand --> IServiceInstallValidator
    InstallServiceCommand --> InstallServiceOptions
    StartServiceCommand --> IServiceManager
    StartServiceCommand --> StartServiceOptions
    StopServiceCommand --> IServiceManager
    StopServiceCommand --> StopServiceOptions
    RestartServiceCommand --> IServiceManager
    RestartServiceCommand --> RestartServiceOptions
    UninstallServiceCommand --> IServiceManager
    UninstallServiceCommand --> UninstallServiceOptions

    BaseCommand --> CommandResult
Loading

Servy.Core (Core Library)

Shared library containing common functionality used across all projects.

Key Responsibilities:

  • Implement common utilities and helper classes
  • Define interfaces and contracts

Core Components:

  • ServiceManager - Provides methods to install, uninstall, start, stop, restart, and update Windows services.
  • ServiceControllerWrapper - Defines an abstraction for controlling and monitoring the status of a Windows service.
  • WindowsServiceApi - Provides an abstraction for invoking native Windows Service API functions.
  • Win32ErrorProvider - Provides access to the last Win32 error code.
  • RotatingStreamWriter - Writes text to a file with automatic log rotation based on file size.

Class Diagram - Servy.Core (Core Library)

classDiagram
  direction LR

  class ProcessPriority {
    <<enum>>
    Idle
    BelowNormal
    Normal
    AboveNormal
    High
    RealTime
  }

  class RecoveryAction {
    <<enum>>
    None
    RestartService
    RestartProcess
    RestartComputer
  }

  class ServiceStartType {
    <<enum>>
    Automatic
    Manual
    Disabled
  }

  class Helper {
    +IsValidPath(path: string): bool
    +CreateParentDirectory(path: string): bool
    +Quote(input: string): string
  }

  class RotatingStreamWriter {
    -FileInfo _file
    -StreamWriter _writer
    -long _rotationSize
    -object _lock
    +RotatingStreamWriter(path: string, rotationSizeInBytes: long)
    -CreateWriter(): StreamWriter
    +WriteLine(line: string)
    -GenerateUniqueFileName(basePath: string): string
    -Rotate()
    +Dispose()
  }

  class IServiceControllerWrapper {
    <<interface>>
    +Status: ServiceControllerStatus
    +Start()
    +Stop()
    +Refresh()
    +WaitForStatus(desiredStatus: ServiceControllerStatus, timeout: TimeSpan)
    +Dispose()
  }

  class IServiceManager {
    <<interface>>
    +InstallService(...)
    +UninstallService(serviceName: string): bool
    +StartService(serviceName: string): bool
    +StopService(serviceName: string): bool
    +RestartService(serviceName: string): bool
  }

  class IWin32ErrorProvider {
    <<interface>>
    +GetLastWin32Error(): int
  }

  class IWindowsServiceApi {
    <<interface>>
    +OpenSCManager(...): IntPtr
    +CreateService(...): IntPtr
    +OpenService(...): IntPtr
    +DeleteService(...): bool
    +CloseServiceHandle(...): bool
    +ControlService(...): bool
    +ChangeServiceConfig(...): bool
    +ChangeServiceConfig2(...): bool
  }

  class NativeMethods {
    <<static>>
    +SERVICE_DESCRIPTION
    +SC_MANAGER_ALL_ACCESS: int
    +SERVICE_ALL_ACCESS: int
    +SERVICE_QUERY_STATUS: int
    +SERVICE_DEMAND_START: int
    +SERVICE_NO_CHANGE: uint
    +SERVICE_CONTROL_STOP: int
    +SERVICE_STATUS
    +OpenSCManager(...): IntPtr
    +CreateService(...): IntPtr
    +OpenService(...): IntPtr
    +DeleteService(...): bool
    +CloseServiceHandle(...): bool
    +ControlService(...): bool
    +ChangeServiceConfig(...): bool
    +ChangeServiceConfig2(...): bool
  }

  class ServiceControllerWrapper {
    -ServiceController _controller
    +ServiceControllerWrapper(serviceName: string)
    +Status: ServiceControllerStatus
    +Start()
    +Stop()
    +Refresh()
    +WaitForStatus(desiredStatus: ServiceControllerStatus, timeout: TimeSpan)
    +Dispose()
  }

  class ServiceManager {
    -Func<string, IServiceControllerWrapper> _controllerFactory
    -IWindowsServiceApi _windowsServiceApi
    -IWin32ErrorProvider _win32ErrorProvider
    +ServiceManager(controllerFactory: Func<string, IServiceControllerWrapper>, windowsServiceApi: IWindowsServiceApi, win32ErrorProvider: IWin32ErrorProvider)
    +UpdateServiceConfig(...): bool
    +SetServiceDescription(serviceHandle: IntPtr, description: string)
    +InstallService(...): bool
    +UninstallService(serviceName: string): bool
    +StartService(serviceName: string): bool
    +StopService(serviceName: string): bool
    +RestartService(serviceName: string): bool
  }

  class Win32ErrorProvider {
    +GetLastWin32Error(): int
  }

  IServiceControllerWrapper <|-- ServiceControllerWrapper
  IServiceManager <|-- ServiceManager
  IWin32ErrorProvider <|-- Win32ErrorProvider
  IWindowsServiceApi <|-- NativeMethods

  ServiceManager --> IServiceControllerWrapper : uses
  ServiceManager --> IWindowsServiceApi : uses
  ServiceManager --> IWin32ErrorProvider : uses
  ServiceManager --> Helper : uses
  ServiceManager --> ProcessPriority : uses
  ServiceManager --> RecoveryAction : uses
  ServiceManager --> ServiceStartType : uses
  ServiceControllerWrapper --> ServiceController : uses
  RotatingStreamWriter --> Helper : uses

  NativeMethods ..> SERVICE_DESCRIPTION
  NativeMethods ..> SERVICE_STATUS

  ServiceManager ..> NativeMethods : uses
Loading

Servy.Infrastructure (Infrastructure layer)

The Infrastructure Layer is implemented in the Servy.Infrastructure project.
It is responsible for all data persistence and retrieval operations, as well as integration with external systems such as configuration files or platform APIs.

Responsibilities

  • Database Access – Interacts with Servy’s SQLite database (Servy.db by default in C:\ProgramData\Servy\db\).
  • Data Persistence – Reads and writes service configurations, logs, and related metadata.
  • Configuration Management – Loads and saves application-level settings from appsettings.json or .exe.config.
  • External Integration – Provides low-level communication with operating system components or external libraries.

Dapper ORM

Servy.Infrastructure uses Dapper, a lightweight Object–Relational Mapper (ORM) for .NET, to map database rows to strongly-typed C# objects.

Key benefits of using Dapper in Servy:

  • Performance – Minimal overhead compared to raw ADO.NET, making it suitable for high-frequency queries.
  • Simplicity – Allows writing SQL directly for clarity and control.
  • Type Safety – Automatically maps query results to Servy’s DTOs and entity classes.

Usage Pattern

  1. Define SQL Queries – Queries are written explicitly in the repository classes.
  2. Execute with DapperIDbConnection is used with Dapper’s extension methods such as Query<T>() and Execute().
  3. Return Mapped Objects – Results are returned as domain entities or DTOs to the Business Logic Layer (Servy.Core).
using (var connection = new SQLiteConnection(connectionString))
{
    return connection.Query<ServiceDto>(
        "SELECT * FROM Services WHERE Name = @Name",
        new { Name = serviceName }
    ).FirstOrDefault();
}

By keeping all persistence logic in Servy.Infrastructure, Servy maintains separation of concerns, ensuring that higher layers (Core, CLI, GUI) remain independent of the underlying storage mechanism.

Servy.Service (Windows Service)

Windows Service executable that wraps and manages target processes.

Key Responsibilities:

  • Act as Windows Service host
  • Launch and manage target executables
  • Handle process monitoring and restart logic
  • Redirect stdout/stderr with rotation
  • Apply process priority and working directory settings
  • Health checks and automatic service recovery
  • Handle service lifecycle operations

Service Lifecycle:

  1. OnStart - Load configuration and start target process
  2. OnStop - Gracefully terminate target process

Class Diagram - Servy.Service (Windows Service)

classDiagram
  direction LR

  class ICommandLineProvider {
    <<interface>>
    +GetArgs(): string[]
  }

  class CommandLineProvider {
    +GetArgs(): string[]
  }

  class StartOptionsParser {
    <<static>>
    +Parse(fullArgs: string[]): StartOptions
  }

  class ILogger {
    <<interface>>
    +Info(message: string)
    +Warning(message: string)
    +Error(message: string, ex: Exception)
  }

  class EventLogLogger {
    -EventLog _eventLog
    +EventLogLogger(source: string)
    +Info(message: string)
    +Warning(message: string)
    +Error(message: string, ex: Exception)
  }

  class NativeMethods {
    <<static>>
    +CreateJobObject(lpJobAttributes: IntPtr, lpName: string): IntPtr
    +SetInformationJobObject(hJob: IntPtr, infoClass: JOBOBJECTINFOCLASS, lpJobObjectInfo: IntPtr, cbJobObjectInfoLength: uint): bool
    +AssignProcessToJobObject(hJob: IntPtr, hProcess: IntPtr): bool
    +CloseHandle(hObject: IntPtr): bool
    +JOBOBJECTINFOCLASS
    +LimitFlags
    +JOBOBJECT_EXTENDED_LIMIT_INFORMATION
    +JOBOBJECT_BASIC_LIMIT_INFORMATION
    +IO_COUNTERS
  }

  class IProcessFactory {
    <<interface>>
    +Create(startInfo: ProcessStartInfo): IProcessWrapper
  }

  class IProcessWrapper {
    <<interface>>
    +ProcessHandle: IntPtr
    +OutputDataReceived: event
    +ErrorDataReceived: event
    +Exited: event
    +Id: int
    +HasExited: bool
    +Handle: IntPtr
    +ExitCode: int
    +EnableRaisingEvents: bool
    +Start()
    +Kill()
    +WaitForExit(milliseconds: int): bool
    +WaitForExit()
    +CloseMainWindow(): bool
    +MainWindowHandle: IntPtr
    +PriorityClass: ProcessPriorityClass
    +BeginOutputReadLine()
    +BeginErrorReadLine()
    +Dispose()
  }

  class ProcessFactory {
    +Create(startInfo: ProcessStartInfo): IProcessWrapper
  }

  class ProcessWrapper {
    -Process _process
    +ProcessHandle: IntPtr
    +ProcessWrapper(psi: ProcessStartInfo)
    +OutputDataReceived: event
    +ErrorDataReceived: event
    +Exited: event
    +Id: int
    +HasExited: bool
    +Handle: IntPtr
    +ExitCode: int
    +MainWindowHandle: IntPtr
    +EnableRaisingEvents: bool
    +PriorityClass: ProcessPriorityClass
    +Start()
    +Kill()
    +WaitForExit(milliseconds: int): bool
    +WaitForExit()
    +CloseMainWindow(): bool
    +BeginOutputReadLine()
    +BeginErrorReadLine()
    +Dispose()
  }

  class IServiceHelper {
    <<interface>>
    +GetServyServiceExecutablePath(): string
    +GetServyRestarterExecutablePath(): string
  }

  class ServiceHelper {
    +GetServyServiceExecutablePath(): string
    +GetServyRestarterExecutablePath(): string
  }

  class StartOptions {
    +ExecutablePath: string
    +ExecutableArgs: string
    +WorkingDirectory: string
    +Priority: ProcessPriorityClass
    +StdOutPath: string
    +StdErrPath: string
    +RotationSizeInBytes: int
    +HeartbeatInterval: int
    +MaxFailedChecks: int
    +RecoveryAction: RecoveryAction
    +ServiceName: string
    +MaxRestartAttempts: int
  }

  class IStreamWriter {
    <<interface>>
    +WriteLine(line: string)
  }

  class IStreamWriterFactory {
    <<interface>>
    +Create(path: string, rotationSizeInBytes: long): IStreamWriter
  }

  class RotatingStreamWriterAdapter {
    -RotatingStreamWriter _writer
    +RotatingStreamWriterAdapter(path: string, rotationSizeInBytes: long)
    +WriteLine(line: string)
  }

  class StreamWriterFactory {
    +Create(path: string, rotationSizeInBytes: long): IStreamWriter
  }

  class ITimer {
    <<interface>>
    +Elapsed: event
    +Interval: double
    +AutoReset: bool
    +Enabled: bool
    +Start()
    +Stop()
    +Dispose()
  }

  class ITimerFactory {
    <<interface>>
    +Create(): ITimer
  }

  class TimerAdapter {
    -Timer _timer
    +TimerAdapter()
    +Elapsed: event
    +Interval: double
    +AutoReset: bool
    +Enabled: bool
    +Start()
    +Stop()
    +Dispose()
  }

  class TimerFactory {
    +Create(): ITimer
  }

  class IPathValidator {
    <<interface>>
    +IsValid(path: string): bool
  }

  class PathValidator {
    +IsValid(path: string): bool
  }

  class Program {
    +Main(args: string[])
  }

  class Service {
    -ILogger _logger
    -IProcessFactory _processFactory
    -IStreamWriterFactory _streamWriterFactory
    -ITimerFactory _timerFactory
    -IPathValidator _pathValidator
    -IProcessWrapper? _process
    -ITimer? _heartbeatTimer
    -int _failedChecksCount
    -StartOptions _options
    +Service(logger: ILogger, processFactory: IProcessFactory, streamWriterFactory: IStreamWriterFactory, timerFactory: ITimerFactory, pathValidator: IPathValidator)
    +OnStart(args: string[])
    +OnStop()
    +OnShutdown()
    -StartProcess()
    -StopProcess()
    -HandleProcessExit(sender: object, e: EventArgs)
    -HandleHeartbeat(sender: object, e: ElapsedEventArgs)
    -LogOutput(sender: object, e: DataReceivedEventArgs)
    -LogError(sender: object, e: DataReceivedEventArgs)
    -PerformRecoveryAction()
  }

  ICommandLineProvider <|-- CommandLineProvider
  ILogger <|-- EventLogLogger
  IProcessFactory <|-- ProcessFactory
  IProcessWrapper <|-- ProcessWrapper
  IServiceHelper <|-- ServiceHelper
  IStreamWriter <|-- RotatingStreamWriterAdapter
  IStreamWriterFactory <|-- StreamWriterFactory
  ITimer <|-- TimerAdapter
  ITimerFactory <|-- TimerFactory
  IPathValidator <|-- PathValidator

  StartOptionsParser ..> StartOptions : creates
  StartOptionsParser ..> RecoveryAction : uses
  StartOptionsParser ..> ProcessPriorityClass : uses

  Service ..> ILogger : uses
  Service ..> IProcessFactory : uses
  Service ..> IStreamWriterFactory : uses
  Service ..> ITimerFactory : uses
  Service ..> IPathValidator : uses
  Service ..> StartOptions : uses
  Service ..> RecoveryAction : uses
  Service ..> ProcessPriorityClass : uses

  Program ..> ICommandLineProvider : uses
  Program ..> StartOptionsParser : uses
  Program ..> ILogger : uses
  Program ..> Service : uses
  Program ..> IProcessFactory : uses
  Program ..> IStreamWriterFactory : uses
  Program ..> ITimerFactory : uses
  Program ..> IPathValidator : uses

  RotatingStreamWriterAdapter ..> Servy.Core.IO.RotatingStreamWriter : uses

  NativeMethods ..> JOBOBJECT_EXTENDED_LIMIT_INFORMATION
  NativeMethods ..> JOBOBJECT_BASIC_LIMIT_INFORMATION
  NativeMethods ..> IO_COUNTERS

  ServiceHelper ..> System.Reflection.Assembly : uses
  ServiceHelper ..> System.IO.Path : uses

  TimerAdapter ..> System.Timers.Timer : uses

  PathValidator ..> Servy.Core.Helpers.Helper : uses
Loading

Servy.Restarter (Restart Manager)

Utility component for handling service restart.

Class Diagram - Servy.Restarter (Restart Manager)

classDiagram
  direction LR

  class IServiceController {
    <<interface>>
    +Status: ServiceControllerStatus
    +WaitForStatus(desiredStatus: ServiceControllerStatus, timeout: TimeSpan)
    +Start()
    +Stop()
    +Dispose()
  }

  class ServiceController {
    -System.ServiceProcess.ServiceController _controller
    +ServiceController(serviceName: string)
    +Status: ServiceControllerStatus
    +Start()
    +Stop()
    +WaitForStatus(desiredStatus: ServiceControllerStatus, timeout: TimeSpan)
    +Dispose()
  }

  IServiceController <|-- ServiceController
  ServiceController --> System.ServiceProcess.ServiceController : wraps
Loading

Design Patterns

The Servy architecture implements several well-established design patterns:

Pattern Usage Location
MVVM Separates the UI (View) from the business logic (Model) using a ViewModel. Extensively used in the Servy UI project for data binding and command handling. Servy/ViewModels, Servy/Models, Servy/MainWindow.xaml
Factory Method Used for creating instances of IServiceControllerWrapper, IProcessWrapper, IStreamWriter, ITimer, and Dapper-based repository objects. Decouples client code from concrete implementations. Servy.Core/Services/ServiceManager.cs, Servy.Service/ProcessManagement/ProcessFactory.cs, Servy.Service/StreamWriters/StreamWriterFactory.cs, Servy.Service/Timers/TimerFactory.cs, Servy.Infrastructure/Repositories/RepositoryFactory.cs
Adapter ServiceControllerWrapper adapts System.ServiceProcess.ServiceController to IServiceControllerWrapper. RotatingStreamWriterAdapter adapts Servy.Core.IO.RotatingStreamWriter to IStreamWriter. TimerAdapter adapts System.Timers.Timer to ITimer. Dapper-based repositories adapt raw SQL queries to strongly-typed DTOs. Servy.Core/Services/ServiceControllerWrapper.cs, Servy.Service/.../RotatingStreamWriterAdapter.cs, Servy.Service/Timers/TimerAdapter.cs, Servy.Infrastructure/Repositories/DapperRepositoryAdapter.cs
Singleton The StartOptionsParser is a static class, providing a single point of access for parsing command-line arguments. Servy.Service/CommandLine/StartOptionsParser.cs
Strategy The Service class uses various interfaces (ILogger, IProcessFactory, IStreamWriterFactory, ITimerFactory, IPathValidator) representing different strategies for logging, process creation, stream writing, timing, and path validation. Implementations can be swapped at runtime. Dapper repositories implement different query strategies depending on the data operation. Servy.Service/Service.cs, Servy.Infrastructure/Repositories/*
Observer The IProcessWrapper defines events (OutputDataReceived, ErrorDataReceived, Exited) that allow other components to observe changes in the process state. Servy.Service/ProcessManagement/IProcessWrapper.cs
Dependency Injection Constructors of many classes (e.g., ServiceManager, ServiceCommands, Service, repositories) take interfaces as parameters. This promotes loose coupling and testability. Throughout Servy.Core, Servy.Service, Servy.Infrastructure, and Servy projects
Repository Provides an abstraction over the SQLite database. Encapsulates Dapper queries and commands, allowing the Core and Service layers to operate on strongly-typed objects without knowing SQL details. Servy.Infrastructure/Repositories/*

Integration Flow

sequenceDiagram
    participant User
    participant WPF as Servy WPF App
    participant Core as Servy.Core
    participant SCM as Windows SCM
    participant Service as Servy.Service
    participant Target as Target Process
    participant Restarter as Servy.Restarter

    User->>WPF: Configure service
    WPF->>Core: Validate configuration
    Core->>WPF: Validation result
    WPF->>SCM: Install service
    SCM->>Service: Create service entry
    WPF->>SCM: Start service
    SCM->>Service: OnStart()
    Service->>Service: Load configuration
    Service->>Target: Start process
    Service->>Service: Begin monitoring
    Service->>Service: Health check
    Note over Target: Process runs
    Service->>Service: Failure detected
    Service->>Target: Restart process
    Service->>Restarter: Restart service
Loading

Technology Stack

Core Technologies

  • .NET 8.0 - Primary development framework
  • .NET Framework 4.8 - Development framework for compatibility with older Windows versions
  • WPF (Windows Presentation Foundation) - User interface
  • Windows API - Service management and system integration
  • Windows Services - Background process hosting

Development Tools

  • C# - Primary programming language
  • Visual Studio - IDE
  • Inno Setup - Installer creation
  • Mermaid - Architecture diagrams

System Requirements

  • Operating System: Windows 7 SP1 / 8 / 10 / 11 (x64) / Windows Server
  • Runtime: .NET 8.0 / .NET Framework 4.8
  • Privileges: Administrator privileges (required for service installation)

Contributing

This architecture documentation is part of the open-source Servy project. Contributions to improve the documentation or codebase are welcome through GitHub issues and pull requests.

For more information about using Servy, see the main README file.

Clone this wiki locally