Files
ColaFlow/colaflow-api
Yaojia Wang 738d32428a fix(backend): Fix database foreign key constraint bug (BUG-002)
Critical bug fix for tenant registration failure caused by incorrect EF Core migration.

## Problem
The AddUserTenantRoles migration generated duplicate columns:
- Application columns: user_id, tenant_id (used by code)
- Shadow FK columns: user_id1, tenant_id1 (incorrect EF Core generation)

Foreign key constraints referenced wrong columns (user_id1/tenant_id1), causing all
tenant registrations to fail with:
```
violates foreign key constraint "FK_user_tenant_roles_tenants_tenant_id1"
```

## Root Cause
UserTenantRoleConfiguration.cs used string column names in HasForeignKey(),
combined with Value Object properties (UserId/TenantId), causing EF Core to
create shadow properties with duplicate names (user_id1, tenant_id1).

## Solution
1. **Configuration Change**:
   - Keep Value Object properties (UserId, TenantId) for application use
   - Ignore navigation properties (User, Tenant) to prevent shadow property generation
   - Let EF Core use the converted Value Object columns for data storage

2. **Migration Change**:
   - Delete incorrect AddUserTenantRoles migration
   - Generate new FixUserTenantRolesIgnoreNavigation migration
   - Drop duplicate columns (user_id1, tenant_id1)
   - Recreate FK constraints referencing correct columns (user_id, tenant_id)

## Changes
- Modified: UserTenantRoleConfiguration.cs
  - Ignore navigation properties (User, Tenant)
  - Use Value Object conversion for UserId/TenantId columns
- Deleted: 20251103135644_AddUserTenantRoles migration (broken)
- Added: 20251103150353_FixUserTenantRolesIgnoreNavigation migration (fixed)
- Updated: IdentityDbContextModelSnapshot.cs (no duplicate columns)
- Added: test-bugfix.ps1 (regression test script)

## Test Results
- Tenant registration: SUCCESS
- JWT Token generation: SUCCESS
- Refresh Token generation: SUCCESS
- Foreign key constraints: CORRECT (user_id, tenant_id)

## Database Schema (After Fix)
```sql
CREATE TABLE identity.user_tenant_roles (
    id uuid PRIMARY KEY,
    user_id uuid NOT NULL,     -- Used by application & FK
    tenant_id uuid NOT NULL,   -- Used by application & FK
    role varchar(50) NOT NULL,
    assigned_at timestamptz NOT NULL,
    assigned_by_user_id uuid,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
);
```

Fixes: BUG-002 (CRITICAL)
Severity: CRITICAL - Blocked all tenant registrations
Impact: Day 5 RBAC feature now working

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 16:07:14 +01:00
..
2025-11-02 23:55:18 +01:00
2025-11-03 14:00:24 +01:00
2025-11-02 23:55:18 +01:00
2025-11-02 23:55:18 +01:00
2025-11-03 14:00:24 +01:00
2025-11-02 23:55:18 +01:00
2025-11-03 11:51:02 +01:00
2025-11-02 23:55:18 +01:00
2025-11-03 11:51:02 +01:00
2025-11-03 11:51:02 +01:00

ColaFlow API

ColaFlow 后端 API 服务 - 基于 .NET 9 的 Modular Monolith + Clean Architecture + DDD + CQRS 实现。

架构亮点

  • Modular Monolith Architecture - 模块化单体架构,清晰的模块边界
  • Clean Architecture - 四层架构设计Domain → Application → Infrastructure → API
  • Domain-Driven Design (DDD) - 领域驱动设计(战术模式)
  • CQRS - 命令查询职责分离MediatR
  • Event Sourcing - 事件溯源(用于审计日志)
  • Architecture Testing - 自动化架构测试NetArchTest

技术栈

  • .NET 9 - 最新的 .NET 平台
  • Entity Framework Core 9 - ORM
  • PostgreSQL 16+ - 主数据库
  • Redis 7+ - 缓存和会话管理
  • MediatR - 中介者模式CQRS 和模块间通信)
  • xUnit - 单元测试框架
  • NetArchTest.Rules - 架构测试
  • Testcontainers - 集成测试

项目结构(模块化单体)

colaflow-api/
├── src/
│   ├── ColaFlow.API/                    # API 层(统一入口)
│   │   └── Program.cs                   # 模块注册和启动
│   │
│   ├── Modules/                         # 业务模块
│   │   ├── ProjectManagement/           # 项目管理模块
│   │   │   ├── ColaFlow.Modules.PM.Domain/
│   │   │   │   ├── Aggregates/          # Project, Epic, Story, Task
│   │   │   │   ├── ValueObjects/        # ProjectId, ProjectKey, etc.
│   │   │   │   ├── Events/              # Domain Events
│   │   │   │   └── Exceptions/          # Domain Exceptions
│   │   │   ├── ColaFlow.Modules.PM.Application/
│   │   │   │   ├── Commands/            # CQRS Commands
│   │   │   │   ├── Queries/             # CQRS Queries
│   │   │   │   └── DTOs/                # Data Transfer Objects
│   │   │   ├── ColaFlow.Modules.PM.Infrastructure/
│   │   │   │   ├── Persistence/         # EF Core Configurations
│   │   │   │   └── Repositories/        # Repository Implementations
│   │   │   └── ColaFlow.Modules.PM.Contracts/
│   │   │       └── Events/              # Integration Events
│   │   │
│   │   ├── Workflow/                    # 工作流模块(待实现)
│   │   ├── UserManagement/              # 用户管理模块(待实现)
│   │   ├── Notifications/               # 通知模块(待实现)
│   │   ├── Audit/                       # 审计模块(待实现)
│   │   └── AI/                          # AI 模块(待实现)
│   │
│   ├── Shared/                          # 共享内核
│   │   └── ColaFlow.Shared.Kernel/
│   │       ├── Common/                  # Entity, ValueObject, AggregateRoot
│   │       ├── Events/                  # DomainEvent
│   │       └── Modules/                 # IModule 接口
│   │
│   └── [Legacy - To be removed]         # 旧的单体结构(迁移中)
│       ├── ColaFlow.Domain/
│       ├── ColaFlow.Application/
│       └── ColaFlow.Infrastructure/
│
├── tests/
│   ├── ColaFlow.ArchitectureTests/      # 架构测试(模块边界)
│   ├── ColaFlow.Domain.Tests/           # 领域层单元测试
│   ├── ColaFlow.Application.Tests/      # 应用层单元测试
│   └── ColaFlow.IntegrationTests/       # 集成测试
└── ColaFlow.sln

Modular Monolith 架构

模块边界规则

┌────────────────────────────────────────────────────┐
│              ColaFlow.API (Entry Point)            │
└─────────────────┬──────────────────────────────────┘
                  │
    ┌─────────────┴─────────────┐
    │                           │
    ▼                           ▼
┌─────────────┐           ┌─────────────┐
│    PM       │           │  Workflow   │  ... (其他模块)
│   Module    │◄─────────►│   Module    │
└─────────────┘           └─────────────┘
      │                         │
      ▼                         ▼
┌─────────────────────────────────────┐
│      Shared.Kernel (Common Base)    │
└─────────────────────────────────────┘

模块通信规则

  1. 禁止直接引用其他模块的 Domain 实体
  2. 允许通过 MediatR 查询其他模块Application Service Integration
  3. 允许通过 Domain Events 解耦通信Event-Driven
  4. 使用 Contracts 项目定义模块对外接口

架构测试

项目包含自动化架构测试,确保模块边界被严格遵守:

dotnet test tests/ColaFlow.ArchitectureTests

测试内容:

  • Domain 层不依赖 Application 和 Infrastructure
  • Domain 层只依赖 Shared.Kernel
  • 模块间不直接引用其他模块的 Domain 实体
  • AggregateRoot 正确继承
  • ValueObject 是不可变的sealed

Clean Architecture 层级依赖

每个模块内部仍然遵循 Clean Architecture

Module Structure:
  API/Controllers ──┐
                    ├──> Application ──> Domain
  Infrastructure ───┘

依赖规则:

  • Domain 层:仅依赖 Shared.Kernel无其他依赖
  • Application 层:依赖 Domain 和 Contracts
  • Infrastructure 层:依赖 Domain 和 Application
  • API 层:依赖所有层

快速开始

前置要求

  • .NET 9 SDK
  • Docker Desktop用于 PostgreSQL 和 Redis
  • IDEVisual Studio 2022 / JetBrains Rider / VS Code

1. 安装依赖

cd colaflow-api
dotnet restore

2. 启动数据库(使用 Docker

从项目根目录启动:

cd ..
docker-compose up -d postgres redis

3. 运行数据库迁移

# 创建迁移Infrastructure 层完成后)
dotnet ef migrations add InitialCreate --project src/ColaFlow.Infrastructure --startup-project src/ColaFlow.API

# 应用迁移
dotnet ef database update --project src/ColaFlow.Infrastructure --startup-project src/ColaFlow.API

4. 运行 API

cd src/ColaFlow.API
dotnet run

API 将运行在:

  • HTTP: http://localhost:5000
  • HTTPS: https://localhost:5001
  • Swagger/Scalar: https://localhost:5001/scalar/v1

5. 运行测试

# 运行所有测试
dotnet test

# 运行单元测试
dotnet test --filter Category=Unit

# 运行集成测试
dotnet test --filter Category=Integration

# 生成覆盖率报告
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover

开发指南

Domain Layer 开发

聚合根示例:

public class Project : AggregateRoot
{
    public ProjectId Id { get; private set; }
    public string Name { get; private set; }

    // 工厂方法
    public static Project Create(string name, string description, UserId ownerId)
    {
        var project = new Project
        {
            Id = ProjectId.Create(),
            Name = name,
            OwnerId = ownerId
        };

        project.AddDomainEvent(new ProjectCreatedEvent(project.Id, project.Name));
        return project;
    }

    // 业务方法
    public void UpdateDetails(string name, string description)
    {
        Name = name;
        Description = description;
        AddDomainEvent(new ProjectUpdatedEvent(Id));
    }
}

Application Layer 开发CQRS

Command 示例:

public sealed record CreateProjectCommand : IRequest<ProjectDto>
{
    public string Name { get; init; }
    public string Description { get; init; }
}

public sealed class CreateProjectCommandHandler : IRequestHandler<CreateProjectCommand, ProjectDto>
{
    public async Task<ProjectDto> Handle(CreateProjectCommand request, CancellationToken cancellationToken)
    {
        // 1. 创建聚合
        var project = Project.Create(request.Name, request.Description, currentUserId);

        // 2. 保存
        await _repository.AddAsync(project, cancellationToken);
        await _unitOfWork.SaveChangesAsync(cancellationToken);

        // 3. 返回 DTO
        return _mapper.Map<ProjectDto>(project);
    }
}

Query 示例:

public sealed record GetProjectByIdQuery : IRequest<ProjectDto>
{
    public Guid ProjectId { get; init; }
}

public sealed class GetProjectByIdQueryHandler : IQueryHandler<GetProjectByIdQuery, ProjectDto>
{
    public async Task<ProjectDto> Handle(GetProjectByIdQuery request, CancellationToken cancellationToken)
    {
        var project = await _context.Projects
            .AsNoTracking()
            .FirstOrDefaultAsync(p => p.Id == request.ProjectId, cancellationToken);

        return _mapper.Map<ProjectDto>(project);
    }
}

API Layer 开发

Controller 示例:

[ApiController]
[Route("api/v1/[controller]")]
public class ProjectsController : ControllerBase
{
    private readonly IMediator _mediator;

    [HttpPost]
    [ProducesResponseType(typeof(ProjectDto), StatusCodes.Status201Created)]
    public async Task<IActionResult> CreateProject([FromBody] CreateProjectCommand command)
    {
        var result = await _mediator.Send(command);
        return CreatedAtAction(nameof(GetProject), new { id = result.Id }, result);
    }

    [HttpGet("{id}")]
    [ProducesResponseType(typeof(ProjectDto), StatusCodes.Status200OK)]
    public async Task<IActionResult> GetProject(Guid id)
    {
        var result = await _mediator.Send(new GetProjectByIdQuery { ProjectId = id });
        return Ok(result);
    }
}

测试策略

测试金字塔

  • 70% 单元测试 - Domain 和 Application 层
  • 20% 集成测试 - API 端点测试
  • 10% E2E 测试 - 关键用户流程

单元测试示例

public class ProjectTests
{
    [Fact]
    public void Create_WithValidData_ShouldCreateProject()
    {
        // Arrange
        var name = "Test Project";
        var ownerId = UserId.Create();

        // Act
        var project = Project.Create(name, "Description", ownerId);

        // Assert
        project.Should().NotBeNull();
        project.Name.Should().Be(name);
        project.DomainEvents.Should().ContainSingle(e => e is ProjectCreatedEvent);
    }
}

集成测试示例

public class ProjectsControllerTests : IntegrationTestBase
{
    [Fact]
    public async Task CreateProject_WithValidData_ShouldReturn201()
    {
        // Arrange
        var command = new CreateProjectCommand { Name = "Test", Description = "Test" };

        // Act
        var response = await _client.PostAsJsonAsync("/api/v1/projects", command);

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Created);
        var project = await response.Content.ReadFromJsonAsync<ProjectDto>();
        project.Should().NotBeNull();
    }
}

代码质量

覆盖率要求

  • 最低要求80%
  • 目标90%+
  • 关键路径100%

代码规范

  • 遵循 C# 编码规范
  • 使用 private 构造函数 + 工厂方法
  • 所有 public 方法必须有 XML 注释
  • 所有业务逻辑必须有单元测试

NuGet 包版本

Domain Layer

  • 无外部依赖

Application Layer

  • MediatR 13.1.0
  • FluentValidation 12.0.0
  • AutoMapper 15.1.0

Infrastructure Layer

  • Microsoft.EntityFrameworkCore 9.0.10
  • Npgsql.EntityFrameworkCore.PostgreSQL 9.0.4
  • StackExchange.Redis 2.9.32

API Layer

  • Serilog.AspNetCore 9.0.0
  • Scalar.AspNetCore 2.9.0

Test Projects

  • xUnit 2.9.2
  • FluentAssertions 8.8.0
  • Moq 4.20.72
  • Testcontainers 4.x

环境变量

创建 src/ColaFlow.API/appsettings.Development.json:

{
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Port=5432;Database=colaflow;Username=colaflow;Password=colaflow_password",
    "Redis": "localhost:6379,password=colaflow_redis_password"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}

API 文档

启动应用后,访问:

  • Scalar UI: https://localhost:5001/scalar/v1
  • OpenAPI JSON: https://localhost:5001/openapi/v1.json

相关文档

下一步开发任务

Infrastructure Layer进行中

  • 配置 EF Core DbContext
  • 创建 Entity Configurations
  • 生成数据库 Migrations
  • 实现 Repository 和 Unit of Work

Application Layer待开发

  • 实现 CQRS Commands
  • 实现 CQRS Queries
  • 配置 MediatR Pipeline Behaviors
  • 实现 FluentValidation Validators

API Layer待开发

  • 实现 REST API Controllers
  • 配置 OpenAPI/Scalar
  • 实现异常处理中间件
  • 配置 JWT 认证

测试(待开发)

  • 编写 Domain 单元测试≥80% 覆盖率)
  • 编写 Application 单元测试
  • 编写 API 集成测试

License

MIT License

团队

ColaFlow Development Team


当前状态: 🟡 Domain Layer 完成Infrastructure 和 Application 层开发中

最后更新: 2025-11-02