Project Architecture
Service Layer (RSSVibe.Services)
- MUST implement all business logic in the
RSSVibe.Services project
- MUST define service interfaces (e.g.,
IAuthService, IFeedService) for dependency injection
- MUST use command/result patterns for service operations
- Services SHOULD be organized by domain area in folders (e.g.,
Auth/, Feeds/)
- MUST inject repositories,
UserManager, and other infrastructure dependencies into services
- SHOULD use primary constructors for service classes
- Service implementations MUST be
internal sealed (only interfaces and models are public)
- Each project MUST provide an
IServiceCollection extension method to register its services
Project Responsibilities
| Project |
Responsibility |
RSSVibe.Contracts |
API request/response DTOs, shared domain models |
RSSVibe.Services |
Business logic, validation, orchestration |
RSSVibe.Data |
Entity models, DbContext, configurations, migrations |
RSSVibe.ApiService |
Minimal API endpoints, routing, middleware |
RSSVibe.Web |
Blazor UI components and pages |
Service Layer Patterns
// Service interface (PUBLIC)
public interface IAuthService
{
Task<RegisterUserResult> RegisterUserAsync(RegisterUserCommand command, CancellationToken ct);
}
// Service implementation with primary constructor (INTERNAL SEALED)
internal sealed class AuthService(
UserManager<ApplicationUser> userManager,
ILogger<AuthService> logger) : IAuthService
{
public async Task<RegisterUserResult> RegisterUserAsync(
RegisterUserCommand command,
CancellationToken ct)
{
// Business logic here
}
}
// Command model (PUBLIC - in same file as service or separate Commands/ folder)
public sealed record RegisterUserCommand(
string Email,
string Password,
string DisplayName,
bool MustChangePassword
);
// Result model (PUBLIC - in same file as service or separate Results/ folder)
public sealed record RegisterUserResult
{
public bool Success { get; init; }
public Guid UserId { get; init; }
public string? Email { get; init; }
public RegistrationError? Error { get; init; }
}
Service Registration Pattern
Each project MUST provide an extension method to register its services
Location: {ProjectName}/Extensions/ServiceCollectionExtensions.cs
// In RSSVibe.Services/Extensions/ServiceCollectionExtensions.cs
namespace RSSVibe.Services.Extensions;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddRssVibeServices(this IServiceCollection services)
{
// Register all services from this project
services.AddScoped<IAuthService, AuthService>();
services.AddScoped<IFeedService, FeedService>();
// ... other services
return services;
}
}
// In Program.cs (RSSVibe.ApiService)
builder.Services.AddRssVibeServices(); // Single call registers all services
Benefits:
- Encapsulates service registration logic within each project
Program.cs remains clean with single method calls per project
- Internal implementations hidden from consuming projects
- Easy to maintain and test service registration
Dependency Injection
- MUST use scoped lifetime for request-specific services
- MUST use singleton lifetime for stateless services
- MUST register services via extension methods (e.g.,
AddRssVibeServices())
- Extension methods SHOULD be named
Add{ProjectName} (e.g., AddRssVibeServices, AddRssVibeDatabase)
- Service implementations MUST be
internal sealed to prevent external instantiation
API Contracts
- MUST define all API request/response models in the
RSSVibe.Contracts project
- API contracts are shared between frontend and backend services via project reference
- MUST use positional records for all contract models (immutability and clarity)
- MUST document contract changes in commit messages and ADRs when adding new endpoints or modifying existing ones
- Contracts include DTOs for API requests, responses, and domain models exposed to clients
- Shared contracts like
PagingDto are placed in the root RSSVibe.Contracts namespace and reused across multiple feature areas (e.g., Feeds, FeedAnalyses, FeedItems) to ensure consistency
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: architecture3description: Service layer patterns, project responsibilities, dependency injection, and API contracts for RSSVibe. Use this skill when creating services, organizing code, or understanding project structure. Use when this capability is needed.4---56# Project Architecture78## Service Layer (`RSSVibe.Services`)910- MUST implement all business logic in the `RSSVibe.Services` project11- MUST define service interfaces (e.g., `IAuthService`, `IFeedService`) for dependency injection12- MUST use command/result patterns for service operations13- Services SHOULD be organized by domain area in folders (e.g., `Auth/`, `Feeds/`)14- MUST inject repositories, `UserManager`, and other infrastructure dependencies into services15- SHOULD use primary constructors for service classes16- Service implementations MUST be `internal sealed` (only interfaces and models are `public`)17- Each project MUST provide an `IServiceCollection` extension method to register its services1819---2021## Project Responsibilities2223| Project | Responsibility |24|---------|---------------|25| `RSSVibe.Contracts` | API request/response DTOs, shared domain models |26| `RSSVibe.Services` | Business logic, validation, orchestration |27| `RSSVibe.Data` | Entity models, DbContext, configurations, migrations |28| `RSSVibe.ApiService` | Minimal API endpoints, routing, middleware |29| `RSSVibe.Web` | Blazor UI components and pages |3031---3233## Service Layer Patterns3435```csharp36// Service interface (PUBLIC)37public interface IAuthService38{39 Task<RegisterUserResult> RegisterUserAsync(RegisterUserCommand command, CancellationToken ct);40}4142// Service implementation with primary constructor (INTERNAL SEALED)43internal sealed class AuthService(44 UserManager<ApplicationUser> userManager,45 ILogger<AuthService> logger) : IAuthService46{47 public async Task<RegisterUserResult> RegisterUserAsync(48 RegisterUserCommand command,49 CancellationToken ct)50 {51 // Business logic here52 }53}5455// Command model (PUBLIC - in same file as service or separate Commands/ folder)56public sealed record RegisterUserCommand(57 string Email,58 string Password,59 string DisplayName,60 bool MustChangePassword61);6263// Result model (PUBLIC - in same file as service or separate Results/ folder)64public sealed record RegisterUserResult65{66 public bool Success { get; init; }67 public Guid UserId { get; init; }68 public string? Email { get; init; }69 public RegistrationError? Error { get; init; }70}71```7273---7475## Service Registration Pattern7677**Each project MUST provide an extension method to register its services**7879**Location**: `{ProjectName}/Extensions/ServiceCollectionExtensions.cs`8081```csharp82// In RSSVibe.Services/Extensions/ServiceCollectionExtensions.cs83namespace RSSVibe.Services.Extensions;8485public static class ServiceCollectionExtensions86{87 public static IServiceCollection AddRssVibeServices(this IServiceCollection services)88 {89 // Register all services from this project90 services.AddScoped<IAuthService, AuthService>();91 services.AddScoped<IFeedService, FeedService>();92 // ... other services9394 return services;95 }96}9798// In Program.cs (RSSVibe.ApiService)99builder.Services.AddRssVibeServices(); // Single call registers all services100```101102**Benefits**:103- Encapsulates service registration logic within each project104- `Program.cs` remains clean with single method calls per project105- Internal implementations hidden from consuming projects106- Easy to maintain and test service registration107108---109110## Dependency Injection111112- MUST use scoped lifetime for request-specific services113- MUST use singleton lifetime for stateless services114- MUST register services via extension methods (e.g., `AddRssVibeServices()`)115- Extension methods SHOULD be named `Add{ProjectName}` (e.g., `AddRssVibeServices`, `AddRssVibeDatabase`)116- Service implementations MUST be `internal sealed` to prevent external instantiation117118---119120## API Contracts121122- MUST define all API request/response models in the `RSSVibe.Contracts` project123- API contracts are shared between frontend and backend services via project reference124- MUST use positional records for all contract models (immutability and clarity)125- MUST document contract changes in commit messages and ADRs when adding new endpoints or modifying existing ones126- Contracts include DTOs for API requests, responses, and domain models exposed to clients127- **Shared contracts** like `PagingDto` are placed in the root `RSSVibe.Contracts` namespace and reused across multiple feature areas (e.g., Feeds, FeedAnalyses, FeedItems) to ensure consistency128129---130> Converted and distributed by [TomeVault](https://tomevault.io/claim/jakoss) — claim your Tome and manage your conversions.131<!-- tomevault:4.0:skill_md:2026-04-11 -->