Feature Development Overview

This guide provides an overview of adding new features to AppBlueprint using Clean Architecture and Domain-Driven Design principles.

📚 Definitions

Domain-Driven Design Components

  • Entity: Core domain objects with unique identity
  • DTO (Data Transfer Objects)
    • Request DTOs: API input models
    • Response DTOs: API output models
  • Interfaces: Contracts defining service behaviors
  • Services: Business logic implementation
  • Repositories: Data persistence abstractions
  • Database
    • Migrations: Version-controlled schema changes
    • Contexts: Entity Framework DbContext configurations

📁 File Structure

├─ .github                            # GitHub workflows
├─ Code                               # Application source code
│  ├─ AppBlueprint                    # SaaS Blueprint projects
│     ├─ ApiService                   # .NET API (REST API)    
│     ├─ AppGateway                   # .NET API Gateway with YARP proxy
│     ├─ AppHost                      # .NET Aspire AppHost
│     ├─ DeveloperCli                 # .NET CLI for developers
│     ├─ ServiceDefaults              # .NET Aspire service defaults
│     ├─ Tests                        # .NET tests (unit, integration, architecture, bunit)
│     ├─ Web                          # .NET Blazor server web app
│     ├─ Shared-Modules               # Clean Architecture shared modules
│       ├─ Api.Client.Sdk             # .NET architecture tests
│       ├─ Application                # .NET architecture tests
│       ├─ Contracts                  # .NET architecture tests
│       ├─ Domain                     # .NET architecture tests
│       ├─ Infrastructure             # .NET architecture tests
│       ├─ Presentation.ApiModule     # .NET architecture tests
│       ├─ SharedKernel               # .NET Aspire shared kernel
│       ├─ UiKit                      # .NET architecture tests

Development Process

Adding a new feature typically involves these steps in order:

1. Domain Layer - Define Business Logic

Create your entities, value objects, and business rules.

  • What: Domain entities with strongly-typed IDs, business methods, enums
  • Where: Shared-Modules/AppBlueprint.Domain/Entities/
  • Why: Encapsulate business logic, ensure type safety

📖 Detailed Guide: Adding Domain Entities

2. Infrastructure Layer - Data Access

Implement repositories for data persistence.

  • What: Repository interfaces and implementations using EF Core
  • Where: Shared-Modules/AppBlueprint.Infrastructure/Repositories/
  • Why: Abstract database operations, testable data access

📖 Detailed Guide: Adding Repositories

3. Presentation Layer - API Endpoints

Expose your feature through REST APIs.

  • What: API Controllers with DTOs, validation, error handling
  • Where: Shared-Modules/AppBlueprint.Presentation.ApiModule/Controllers/
  • Why: Provide external interface, protect internal implementation

📖 Detailed Guide: Creating API Controllers

4. UI Layer - User Interface (Optional)

Build Blazor components for user interaction.

  • What: Blazor components with Tailwind CSS
  • Where: AppBlueprint.Web/Components/
  • Why: User-friendly interface for your feature

📖 Detailed Guide: Creating Blazor Components

5. Tests - Quality Assurance

Write comprehensive tests for your feature.

  • What: Unit, integration, and API tests
  • Where: AppBlueprint.Tests/Features/
  • Why: Ensure correctness, prevent regressions

📖 Detailed Guide: Writing Tests

Quick Example: Project Management Feature

Here's what implementing a complete "Project" feature looks like:

Layer Implementation
Domain Project entity with ProjectId, Archive(), Activate() methods
Infrastructure IProjectRepository with GetByTeamIdAsync(), AddAsync(), etc.
Presentation ProjectController with GET, POST, PUT, PATCH, DELETE endpoints
UI ProjectList.razor and ProjectForm.razor with Tailwind CSS styling
Tests Unit tests for domain logic, integration tests for repository

Architecture Layers

AppBlueprint follows Clean Architecture with these layers:

┌─────────────────────────────────────────┐
│          Presentation Layer              │ ← API Controllers, Blazor Pages
│   (Web, ApiService, ApiModule)          │
└──────────────────┬──────────────────────┘
                   │
┌──────────────────▼──────────────────────┐
│         Application Layer                │ ← Use Cases, DTOs, Interfaces
│  (Commands, Queries, Services)          │
└──────────────────┬──────────────────────┘
                   │
┌──────────────────▼──────────────────────┐
│          Domain Layer                    │ ← Business Logic, Entities
│  (Entities, Value Objects, Enums)       │
└──────────────────┬──────────────────────┘
                   │
┌──────────────────▼──────────────────────┐
│       Infrastructure Layer               │ ← Data Access, External APIs
│  (DbContext, Repositories, Services)    │
└──────────────────────────────────────────┘

Key Principle: Dependencies point inward. Domain layer has no dependencies.

Best Practices Checklist

Before submitting your feature:

  • ✅ Use strongly-typed IDs (no string or Guid for entity IDs)
  • ✅ Implement factory methods for entity creation
  • ✅ Keep business logic in domain entities
  • ✅ Use repository pattern for data access
  • ✅ Write tests for all business logic
  • ✅ Add XML documentation comments
  • ✅ Use proper null checking (is null, ThrowIfNull)
  • ✅ Include cancellation tokens in async methods
  • ✅ Return appropriate HTTP status codes
  • ✅ Validate input at API boundaries

Common Patterns

Strongly-Typed IDs

public readonly record struct ProjectId
{
    public Ulid Value { get; }
    public static ProjectId NewId() => new(Ulid.NewUlid());
}

Factory Methods

public static Project Create(string name, TeamId teamId)
{
    ArgumentException.ThrowIfNullOrEmpty(name);
    return new Project { Id = ProjectId.NewId(), Name = name, TeamId = teamId };
}

Repository Interface

public interface IProjectRepository
{
    Task<Project?> GetByIdAsync(ProjectId id, CancellationToken ct = default);
    Task AddAsync(Project project, CancellationToken ct = default);
}

API Controller

[HttpGet("{id}")]
public async Task<ActionResult<ProjectDto>> GetProject(string id)
{
    if (!ProjectId.TryParse(id, out var projectId))
        return BadRequest("Invalid ID");
    
    var project = await _repository.GetByIdAsync(projectId);
    return project is null ? NotFound() : Ok(ToDto(project));
}

Development Workflow

  1. Plan - Identify entities, relationships, and use cases
  2. Domain - Create entities with business logic
  3. Database - Configure EF Core, create migration
  4. Repository - Implement data access layer
  5. API - Create controllers and DTOs
  6. Test - Write comprehensive tests
  7. UI - Build Blazor components (if needed)
  8. Review - Run tests, check code quality
  9. Deploy - Merge PR, deploy changes

Getting Help

  • Detailed Guides - Click the links above for step-by-step instructions
  • Code Examples - Check existing features: TodoEntity, TeamEntity
  • GitHub Discussions - Ask questions at saas-factory-labs/Saas-Factory

Next Steps

Start with the domain layer:

Or jump to a specific guide: