0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

簡易工程進捗管理システムの改良①~フェーズ1 プロジェクト作成・DB設計・API構成整理~

0
Last updated at Posted at 2026-09-27

次の記事

簡易工程進捗管理システムの改良

前回、Web開発技術の学習を主目的とし、作業・工程の進捗状況をWebブラウザから登録・確認・更新できる簡易工程進捗管理システムを作成した。

前回開発したシステム

今回は改良版の開発を行う。

開発の流れ

フェーズ1 プロジェクト作成・DB設計・API構成整理

Controller → Service → Repository → EF Core という構成を作り、DTOやDIも整理する。

フェーズ2 認証・認可

ログイン、JWT、Admin / WorkerなどのRole制御を実装する。

フェーズ3 業務機能の実装

作業・工程・担当者などのCRUDを、整理したAPI構成で実装する。

フェーズ4 Docker化

Next.js / ASP.NET Core / PostgreSQLをDocker Composeで起動できるようにする。

フェーズ5 Next.js フロントエンド作成

フェーズ6 リアルタイム更新

SignalRを使い、別端末での状態変更を画面へ即時反映させる。

フェーズ7 テスト

xUnit、Integration Test、Playwrightを順番に追加する。

フェーズ8 デプロイ

最後に公開環境を整える。

全体構成

ProductionProgress.sln
│
├─ ProductionProgress.Api
│   └─ Controller / HTTP / DI設定
│
├─ ProductionProgress.Application
│   └─ Service / DTO / Interface
│
├─ ProductionProgress.Domain
│   └─ Entity / 業務ルール
│
└─ ProductionProgress.Infrastructure
    └─ AppDbContext / Repository / EF Core / PostgreSQL

Visual Studioでソリューション作成

Visual Studio → 新しいプロジェクトの作成 → 空のソリューション
名前:ProductionProgress
場所:C:\task-management-system-v2\backend

APIプロジェクト作成

ソリューションを右クリック→追加→新しいプロジェクト→ASP.NET Core Web API
プロジェクト名:ProductionProgress.Api
Framework:.NET 10

Class Libraryを3つ作成

ソリューションを右クリック→追加→新しいプロジェクト→クラスライブラリ
※ .NetFrameworkは選ばない

プロジェクト名:
ProductionProgress.Application
ProductionProgress.Domain
ProductionProgress.Infrastructure

プロジェクト参照を設定

プロジェクトを右クリック→追加→プロジェクト参照

Api
 ├──→ Application
 └──→ Infrastructure

Infrastructure
 ├──→ Application
 └──→ Domain

Application
 └──→ Domain

Domain
 └──→ なし

Swagger UIを追加する

Apiを右クリック→NuGetパッケージの管理→Swashbuckle.AspNetCore

DomainにWorkTaskStatus enumを作る

Domain→Enumsフォルダ→WorkTaskState.cs

namespace ProductionProgress.Domain.Enums;

/// <summary>
/// 作業の進捗状態を表す。
///
/// DBには整数値として保存する。
/// 0 = 未着手
/// 1 = 作業中
/// 2 = 完了
/// </summary>
public enum WorkTaskStatus
{
    /// <summary>
    /// まだ作業を開始していない状態。
    /// </summary>
    NotStarted = 0,

    /// <summary>
    /// 現在作業中の状態。
    /// </summary>
    InProgress = 1,

    /// <summary>
    /// 作業が完了した状態。
    /// </summary>
    Completed = 2
}

DomainにWorkTaskを作る

Domain→Entitiesフォルダ→WorkTask.cs

using ProductionProgress.Domain.Enums;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ProductionProgress.Domain.Entities;

/// <summary>
/// 工程で管理する作業を表すエンティティ。
///
/// Table属性・Column属性を使用して、
/// PostgreSQLのテーブル名・列名との対応を定義する。
/// </summary>
[Table("work_tasks")]
public class WorkTask
{
    /// <summary>
    /// 作業を一意に識別するID。
    /// PostgreSQLではid列に対応する。
    /// </summary>
    [Key]
    [Column("id")]
    public int Id { get; set; }

    /// <summary>
    /// 作業名。
    /// 最大200文字、必須項目とする。
    /// </summary>
    [Required]
    [MaxLength(200)]
    [Column("task_name")]
    public string TaskName { get; set; } = string.Empty;

    /// <summary>
    /// 作業の進捗状態。
    ///
    /// C#ではWorkTaskStatusとして扱うが、
    /// PostgreSQLには整数値として保存される。
    /// </summary>
    [Column("status")]
    public WorkTaskStatus Status { get; set; }

    /// <summary>
    /// 作業予定日。
    /// nullの場合は予定日未設定を表す。
    /// </summary>
    [Column("planned_date")]
    public DateOnly? PlannedDate { get; set; }

    /// <summary>
    /// データ作成日時。
    /// </summary>
    [Column("created_at")]
    public DateTime CreatedAt { get; set; }

    /// <summary>
    /// 最終更新日時。
    /// </summary>
    [Column("updated_at")]
    public DateTime UpdatedAt { get; set; }
}

InfrastructureにEF Coreを導入

Infrastructureを右クリック→NuGetパッケージの管理
次をインストール

Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.Design
Microsoft.EntityFrameworkCore.Tools
Npgsql.EntityFrameworkCore.PostgreSQL

AppDbContextを作成

Infrastructure→Dataフォルダ→AppDbContext.cs

using Microsoft.EntityFrameworkCore;
using ProductionProgress.Domain.Entities;

namespace ProductionProgress.Infrastructure.Data;

/// <summary>
/// PostgreSQLとの接続を管理するDbContext。
///
/// Entity Framework Coreを使用して、
/// C#のEntityとPostgreSQLのテーブルを橋渡しする。
/// </summary>
public class AppDbContext : DbContext
{
    /// <summary>
    /// Program.csで設定したDbContextOptionsを受け取る。
    /// 接続先DBやDBプロバイダーなどの情報が含まれる。
    /// </summary>
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options)
    {
    }

    /// <summary>
    /// PostgreSQLのwork_tasksテーブルを操作するためのDbSet。
    /// </summary>
    public DbSet<WorkTask> WorkTasks => Set<WorkTask>();
}

RepositoryのInterfaceを作る

Application→Interfacesフォルダ→ITaskRepository.cs

using ProductionProgress.Domain.Entities;

namespace ProductionProgress.Application.Interfaces;

/// <summary>
/// タスクのデータ取得方法を定義するインターフェース。
///
/// Application層は、
/// 「PostgreSQLから取得する」
/// 「EF Coreを使用する」
/// といった具体的な実装を知らない。
///
/// そのため、InterfaceだけをApplication側に置く。
/// </summary>
public interface ITaskRepository
{
    /// <summary>
    /// 登録されているタスクをすべて取得する。
    /// </summary>
    Task<IReadOnlyList<WorkTask>> GetAllAsync();
}

Repository本体を作る

Infrastructure→Repositoriesフォルダ→TaskRepository.cs

using Microsoft.EntityFrameworkCore;
using ProductionProgress.Application.Interfaces;
using ProductionProgress.Domain.Entities;
using ProductionProgress.Infrastructure.Data;

namespace ProductionProgress.Infrastructure.Repositories;

/// <summary>
/// タスクデータをPostgreSQLから取得するRepository。
///
/// ITaskRepositoryの具体的な実装をInfrastructure層に置くことで、
/// Application層からDBアクセス処理を分離する。
/// </summary>
public class TaskRepository : ITaskRepository
{
    private readonly AppDbContext _dbContext;

    /// <summary>
    /// DIによってAppDbContextを受け取る。
    /// </summary>
    public TaskRepository(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    /// <summary>
    /// タスク一覧をDBから取得する。
    /// </summary>
    public async Task<IReadOnlyList<WorkTask>> GetAllAsync()
    {
        // AsNoTrackingを使用すると、
        // 取得したデータをEF Coreが変更監視しない。
        //
        // 一覧表示のような「読み取りだけ」の処理では、
        // 不要な監視をしないことで処理を軽くできる。
        return await _dbContext.WorkTasks
            .AsNoTracking()
            .OrderBy(x => x.Id)
            .ToListAsync();
    }
}

DTOを作る

Application→Dtosフォルダ→TaskDto.cs

namespace ProductionProgress.Application.Dtos;

/// <summary>
/// APIからタスク情報を返すためのDTO。
///
/// DBのEntityをそのまま外部に公開せず、
/// APIで必要なデータだけを返すために使用する。
/// </summary>
public class TaskDto
{
    public int Id { get; set; }

    public string TaskName { get; set; } = string.Empty;

    public WorkTaskStatus  Status { get; set; }

    public DateOnly? PlannedDate { get; set; }

    public DateTime CreatedAt { get; set; }

    public DateTime UpdatedAt { get; set; }
}

Serviceを作る

Application→Servicesフォルダ→ITaskService.cs

using ProductionProgress.Application.Dtos;

namespace ProductionProgress.Application.Services;

/// <summary>
/// タスクに対して行える業務処理を定義する。
/// </summary>
public interface ITaskService
{
    /// <summary>
    /// タスク一覧を取得する。
    /// </summary>
    Task<IReadOnlyList<TaskDto>> GetTasksAsync();
}

TaskService.cs

using ProductionProgress.Application.Dtos;
using ProductionProgress.Application.Interfaces;

namespace ProductionProgress.Application.Services;

/// <summary>
/// タスクに関する業務処理を担当するService。
///
/// Controllerから直接Repositoryを呼ばず、
/// Serviceを経由することで、
/// 業務ロジックをControllerから分離する。
/// </summary>
public class TaskService : ITaskService
{
    private readonly ITaskRepository _taskRepository;

    /// <summary>
    /// DIによってRepositoryを受け取る。
    /// </summary>
    public TaskService(ITaskRepository taskRepository)
    {
        _taskRepository = taskRepository;
    }

    /// <summary>
    /// タスク一覧を取得する。
    /// </summary>
    public async Task<IReadOnlyList<TaskDto>> GetTasksAsync()
    {
        // DBからDomain Entityを取得する。
        var tasks = await _taskRepository.GetAllAsync();

        // EntityをそのままAPIへ返さず、
        // API公開用のDTOへ変換する。
        return tasks
            .Select(task => new TaskDto
            {
                Id = task.Id,
                TaskName = task.TaskName,
                Status = task.Status,
                PlannedDate = task.PlannedDate,
                CreatedAt = task.CreatedAt,
                UpdatedAt = task.UpdatedAt
            })
            .ToList();
    }
}

Controllerを作る

Api→Controllersフォルダ→TasksController.cs

using Microsoft.AspNetCore.Mvc;
using ProductionProgress.Application.Dtos;
using ProductionProgress.Application.Services;

namespace ProductionProgress.Api.Controllers;

/// <summary>
/// タスク操作用のWeb API。
///
/// ControllerはHTTPリクエストを受け付ける入口だけを担当し、
/// DBアクセスや業務処理はServiceへ任せる。
/// </summary>
[ApiController]
[Route("api/tasks")]
public class TasksController : ControllerBase
{
    private readonly ITaskService _taskService;

    /// <summary>
    /// DIによってTaskServiceを受け取る。
    /// </summary>
    public TasksController(ITaskService taskService)
    {
        _taskService = taskService;
    }

    /// <summary>
    /// タスク一覧を取得する。
    ///
    /// GET /api/tasks
    /// </summary>
    [HttpGet]
    public async Task<ActionResult<IReadOnlyList<TaskDto>>> GetTasks()
    {
        var tasks = await _taskService.GetTasksAsync();

        return Ok(tasks);
    }
}

PostgreSQL接続を設定する

A5:SQL Mk-2(以下、A5M2)でデフォルトDBに接続し、SQLエディタで以下を実行してDBを作成する。

CREATE DATABASE production_progress_v2;

Apiを右クリック→ユーザー シークレットの管理
ここはGit管理されないので、GitHub公開用でも問題なし。

{
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Port=5432;Database=production_progress_v2;Username=postgres;Password=自分のパスワード"
  }
}

Program.csを設定する

ApiのProgram.csを編集する。

using Microsoft.EntityFrameworkCore;
using ProductionProgress.Application.Interfaces;
using ProductionProgress.Application.Services;
using ProductionProgress.Infrastructure.Data;
using ProductionProgress.Infrastructure.Repositories;

var builder = WebApplication.CreateBuilder(args);

// ------------------------------------------------------------
// Controllerを使用できるようにする。
// ------------------------------------------------------------
builder.Services.AddControllers();

// ------------------------------------------------------------
// SwaggerでAPI情報を取得できるようにする。
// ------------------------------------------------------------
builder.Services.AddEndpointsApiExplorer();

// ------------------------------------------------------------
// Swaggerドキュメントを生成するサービスをDIへ登録する。
//
// 今回のエラーはこの登録がないため、
// ISwaggerProviderを取得できなかったことが原因。
// ------------------------------------------------------------
builder.Services.AddSwaggerGen();

// ------------------------------------------------------------
// PostgreSQLの接続文字列を取得する。
// ------------------------------------------------------------
var connectionString =
    builder.Configuration.GetConnectionString("DefaultConnection")
    ?? throw new InvalidOperationException(
        "ConnectionStrings:DefaultConnection が設定されていません。");

// ------------------------------------------------------------
// PostgreSQL用のDbContextをDIへ登録する。
// ------------------------------------------------------------
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseNpgsql(connectionString));

// ------------------------------------------------------------
// RepositoryをDIへ登録する。
// ------------------------------------------------------------
builder.Services.AddScoped<ITaskRepository, TaskRepository>();

// ------------------------------------------------------------
// ServiceをDIへ登録する。
// ------------------------------------------------------------
builder.Services.AddScoped<ITaskService, TaskService>();

var app = builder.Build();

// ------------------------------------------------------------
// 開発環境の場合のみSwaggerを有効にする。
// ------------------------------------------------------------
if (app.Environment.IsDevelopment())
{
    // swagger.jsonを生成する。
    app.UseSwagger();

    // Swagger UIを表示する。
    app.UseSwaggerUI();
}

// ------------------------------------------------------------
// HTTPアクセスをHTTPSへリダイレクトする。
// 開発中はSwaggerからHTTP APIを直接確認するため一旦無効化する。
// ------------------------------------------------------------
// app.UseHttpsRedirection();

app.MapControllers();

app.Run();

Migrationを作成

まず、Microsoft.OpenApiの脆弱性警告を解消する。
Apiを右クリック→NuGet パッケージの管理→Microsoft.OpenApi
2.12.2をインストールする。

次に、Microsoft.EntityFrameworkCore.Relationalのバージョン競合を解消する。
Infrastructureを右クリック→NuGet パッケージの管理→
Microsoft.EntityFrameworkCore.Relational

10.0.12

をインストール。

次に、Microsoft.EntityFrameworkCore.Designを追加インストールする。
Apiを右クリック→NuGet パッケージの管理→Microsoft.EntityFrameworkCore.Design

10.0.12

をインストール。

その後、下記を実行する。
ツール→NuGet パッケージ マネージャー→パッケージ マネージャー コンソール

Add-Migration InitialCreate -Project ProductionProgress.Infrastructure -StartupProject ProductionProgress.Api
Update-Database -Project ProductionProgress.Infrastructure -StartupProject ProductionProgress.Api

動作確認

Apiを起動。
ブラウザで下記を実行。

http://localhost:xxxx/swagger

xxxx:ポート番号。
Apiを起動して出力される画面で、

Now listening on: http://localhost:xxxx

のxxxxがポート番号。

GET /api/Tasks→try it out→Execute

Code:200
Response body:[]

が返ってくればOK。

つまずいた点

Add-MigrationでNU1903が発生した

現象

Migration作成

Add-Migration InitialCreate -Project ProductionProgress.Infrastructure -StartupProject ProductionProgress.Api

で、警告が出た。

warning NU1903:
パッケージ 'Microsoft.OpenApi' 2.0.0 に
既知の高重大度の脆弱性があります。

原因

Microsoft.OpenApi 2.0.0 が使用されており、既知の脆弱性警告が発生していた。

対処

ProductionProgress.ApiのMicrosoft.OpenApiを更新した。

Microsoft.OpenApi
2.0.0
↓
2.12.2

Microsoft.EntityFrameworkCore.Relationalのバージョン競合

現象

ビルドすると、以下の警告が出た。

Microsoft.EntityFrameworkCore.Relational
Version=10.0.4.0

Microsoft.EntityFrameworkCore.Relational
Version=10.0.12.0

の間で競合が発生しました。

原因

Package Manager Consoleで以下を実行。

dotnet package list --include-transitive

すると、ProductionProgress.Apiでは以下の状態。

Microsoft.EntityFrameworkCore                  10.0.12
Microsoft.EntityFrameworkCore.Relational       10.0.4
Npgsql.EntityFrameworkCore.PostgreSQL          10.0.3

一方、ProductionProgress.Infrastructure は、

Microsoft.EntityFrameworkCore                  10.0.12
Microsoft.EntityFrameworkCore.Relational       10.0.12
Npgsql.EntityFrameworkCore.PostgreSQL          10.0.3

になっていた。

Microsoft.EntityFrameworkCore.Relationalは直接追加していなくても、Npgsqlなどの依存関係として自動的に入る。

対処

ProductionProgress.Infrastructureに明示的に、

Microsoft.EntityFrameworkCore.Relational
10.0.12

を追加した。

Add-MigrationでMicrosoft.EntityFrameworkCore.Designがないと言われた

現象

Migrationを作成すると、次のエラーが出。

Your startup project 'ProductionProgress.Api'
doesn't reference Microsoft.EntityFrameworkCore.Design.

原因

コマンドでは、

-StartupProject ProductionProgress.Api

を指定している。
EF Core ToolsはStartupProjectを実際に使用して、以下の情報を取得する。

Program.cs
DI設定
ConnectionString
DbContext設定

そのためStartupProject側にも Microsoft.EntityFrameworkCore.Designが必要。

対処

ProductionProgress.Api に、

Microsoft.EntityFrameworkCore.Design
10.0.12

を追加。

Swaggerを開くとISwaggerProviderエラー

Swaggerを開くと、以下のエラーが出た。

InvalidOperationException:

Unable to resolve service for type
'Swashbuckle.AspNetCore.Swagger.ISwaggerProvider'

while attempting to Invoke middleware
'Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware'.

原因

Program.cs に、

app.UseSwagger();
app.UseSwaggerUI();

は書いてたが、

builder.Services.AddSwaggerGen();

を登録していなかった。
UseSwagger()はSwaggerを使用する側だが、Swagger情報を生成するサービスがDIに登録されていないため、ISwaggerProviderを取得できなかった。

対処

以下を追加。

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?