Claude-skill-registry layer-services
Use when creating or modifying code in nomarr/services/. Services own runtime wiring, long-lived resources (DB, queues, workers), and call workflows. No complex business logic here.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/layer-services" ~/.claude/skills/majiayu000-claude-skill-registry-layer-services && rm -rf "$T"
skills/data/layer-services/SKILL.mdServices Layer
Purpose: Own runtime wiring and long-lived resources (config, DB, queues, workers) and expose a clean API for interfaces.
Services are:
- Dependency coordinators (wire config, DB, ML backends, queues, workers)
- Thin orchestrators (call workflows, aggregate results)
- DTO providers (shape data for interfaces)
No complex business logic lives here. That belongs in workflows.
File and Package Naming
Single-File Services
For simple services, use a single file ending in
_svc.py:
nomarr/services/domain/processing_svc.py → ProcessingService nomarr/services/domain/analytics_svc.py → AnalyticsService
Service Packages
For complex services with multiple concerns, use a package (folder) ending in
_svc:
nomarr/services/domain/library_svc/ ├── __init__.py # Exports LibraryService ├── admin.py # LibraryAdminMixin ├── scan.py # LibraryScanMixin ├── query.py # LibraryQueryMixin ├── files.py # LibraryFilesMixin └── config.py # LibraryServiceConfig dataclass
Rules for service packages:
- Package folder must end in
_svc - Internal files do NOT need
suffix_svc.py - The
exports the composed__init__.py
class<Domain>Service - Internal classes (mixins, config) don't follow
pattern<Domain>Service
Infrastructure Packages
Some folders in
services/infrastructure/ are support packages, not services:
nomarr/services/infrastructure/workers/ ├── __init__.py └── discovery_worker.py # DiscoveryWorker(Process) - runner process
These are exempt from
_svc.py naming since they're not services themselves.
Worker Processes (Runners)
services/infrastructure/workers/ contains runner processes — multiprocessing.Process subclasses that execute work in separate subprocesses.
Why they live here:
- Spawned and managed by
(co-location with their manager)WorkerSystemService - Not components (they call workflows, which components cannot do)
- Not workflows (they contain the execution loop, not just orchestration)
- They are internal entrypoints, similar to CLI or API routes
Architectural exemptions for workers:
| Normal Service Rule | Worker Exemption |
|---|---|
| Services are thin orchestrators | Workers contain execution loops |
| Services call workflows, not components directly | Workers may call both |
Files must end in | Worker files end in |
Classes must end in | Worker classes end in |
Rationale: The subprocess boundary requires self-contained, picklable process classes. Fragmenting them to "follow the rules" would increase complexity without benefit. The architecture rules exist to improve maintainability and code reuse — workers don't need reuse, they need reliability.
Worker file naming:
— Process subclass definitions*_worker.py- Classes should be named
(e.g.,<Domain>Worker
)DiscoveryWorker
Allowed Imports
# ✅ Allowed from nomarr.workflows import scan_library_workflow, process_file_workflow from nomarr.persistence import Database from nomarr.components.ml import MLBackend from nomarr.helpers.dto import LibraryDict, ProcessResult
Forbidden Imports
# ❌ NEVER import these in services from nomarr.interfaces import ... # Services don't know about HTTP/CLI from fastapi import HTTPException # No HTTP semantics from pydantic import BaseModel # No Pydantic models
Service Method Naming
All public methods use
<verb>_<noun>:
# ✅ Good get_library() list_libraries() scan_library() queue_file_for_tagging() start_processing() stop_workers() # ❌ Bad api_get_library() # No transport prefixes get_library_for_admin() # No audience suffixes
Allowed Verbs
- Read:
,get_
,list_
,exists_
,count_fetch_ - Write:
,create_
,update_
,delete_
,set_rename_ - Domain:
,scan_
,tag_
,queue_
,start_
,stop_
,sync_
,import_export_ - Boolean:
,enable_disable_
Complexity Rule: DI + Orchestration Only
A service method should:
- Collect dependencies
- Call workflow(s)
- Return result
Extract to workflow when you see:
- Loops
- Branching logic
- Multi-step operations
- Data transformations
# ✅ Good - orchestration only def process_file(self, file_id: str) -> ProcessResult: file_path = self._resolve_file_path(file_id) return process_file_workflow( db=self.db, file_path=file_path, models_dir=self.config.models_dir, ) # ❌ Bad - business logic in service def process_file(self, file_id: str) -> ProcessResult: file_path = self._resolve_file_path(file_id) if self._should_skip(file_path): # ← Logic belongs in workflow return ProcessResult(skipped=True) embeddings = compute_embeddings(file_path) # ← Direct component call # ... more logic
DTO Requirements
Public methods returning structured data must return DTOs.
# ✅ Correct - DTO return def get_job(self, job_id: int) -> JobDict | None: ... # ✅ Correct - trivial return (no DTO needed) def is_enabled(self) -> bool: ... def get_count(self) -> int: ... # ❌ Wrong - returning raw dict def get_job(self, job_id: int) -> dict[str, Any]: ...
DTO Placement
- Single-service DTOs: Define in the service file
- Cross-layer DTOs: Must live in
helpers/dto/<domain>.py
Long-Lived Resources
Services own:
- DB connections (
)Database - Config snapshots (
)ConfigService - ML backends
- Queue handles
- Worker managers
Use constructor injection:
class LibraryService: def __init__(self, db: Database, config: ConfigService): self.db = db self.config = config
Validation Checklist
Before committing service code, verify:
- Does this file import from interfaces? → Violation
- Does this file import FastAPI, HTTPException, or Pydantic? → Violation
- Does this method contain loops, branching, or computation? → Extract to workflow
- Does this method call components directly? → Should call workflow instead
- Are public methods returning DTOs for structured data? → Required
- Is the method name
? → Required pattern<verb>_<noun>
Layer Scripts
This skill includes validation scripts in
.github/skills/layer-services/scripts/:
lint.py
lint.pyRuns all linters on the services layer:
python .github/skills/layer-services/scripts/lint.py
Executes: ruff, mypy, vulture, bandit, radon, lint-imports
check_naming.py
check_naming.pyValidates services naming conventions:
python .github/skills/layer-services/scripts/check_naming.py
Checks:
- Standalone service files must end in
_svc.py - Service packages (folders ending in
) exempt their internal files from suffix rule_svc/ - Infrastructure packages (e.g.,
) are exempt from service naming rulesworkers/ - Classes must end in
Service - Methods must follow
pattern<verb>_<noun>
Package vs File:
- Simple service:
→processing_svc.pyProcessingService - Complex service:
folder withlibrary_svc/
,admin.py
,scan.py
mixinsquery.py