🔌 用 ASP.NET Core 建立微服務 API
📌 Minimal API vs Controller-based API
ASP.NET Core 提供兩種建立 API 的方式:
// ── 方式 1:Minimal API(簡潔、適合小型微服務) ──
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/api/products", async (ProductDbContext db) =>
await db.Products.ToListAsync());
app.MapGet("/api/products/{id}", async (int id, ProductDbContext db) =>
await db.Products.FindAsync(id) is Product p
? Results.Ok(p)
: Results.NotFound());
app.Run();
// ── 方式 2:Controller-based API(結構化、適合大型服務) ──
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductService _service;
public ProductsController(IProductService service) => _service = service;
[HttpGet]
public async Task<ActionResult<List<ProductDto>>> GetAll()
=> Ok(await _service.GetAllAsync());
[HttpGet("{id}")]
public async Task<ActionResult<ProductDto>> GetById(int id)
{
var product = await _service.GetByIdAsync(id);
return product is null ? NotFound() : Ok(product);
}
}
| 比較 | Minimal API | Controller-based |
|---|---|---|
| 程式碼量 | 少 | 多 |
| 學習曲線 | 低 | 中 |
| 適用場景 | 小型微服務 | 大型、複雜的服務 |
| 過濾器 | EndpointFilter | ActionFilter |
| API 版本控制 | 支援 | 支援 |
📌 建立第一個微服務 API
步驟 1:建立專案
# 建立方案與專案
dotnet new sln -n EShop
dotnet new webapi -n ProductService --no-https
dotnet sln add ProductService/ProductService.csproj
# 加入必要套件
cd ProductService
dotnet add package Microsoft.EntityFrameworkCore.Npgsql
dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection
dotnet add package AspNetCore.HealthChecks.NpgSql
步驟 2:定義領域模型
// Models/Product.cs
namespace ProductService.Models;
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public decimal Price { get; set; }
public int StockQuantity { get; set; }
public string Category { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}
📌 RESTful 設計原則
RESTful API 設計規範:
├── 使用名詞而非動詞:/api/products(不是 /api/getProducts)
├── 使用複數形式:/api/products(不是 /api/product)
├── 使用 HTTP 方法表達操作:
│ ├── GET /api/products → 取得列表
│ ├── GET /api/products/42 → 取得單筆
│ ├── POST /api/products → 建立
│ ├── PUT /api/products/42 → 完整更新
│ ├── PATCH /api/products/42 → 部分更新
│ └── DELETE /api/products/42 → 刪除
├── 使用正確的 HTTP 狀態碼
└── 使用巢狀路由表達關係:/api/orders/42/items
📌 DTO 與 AutoMapper
// DTOs/ProductDto.cs — 回傳給客戶端的資料
public record ProductDto(
int Id,
string Name,
string Description,
decimal Price,
string Category,
bool IsActive);
// DTOs/CreateProductDto.cs — 建立時的輸入
public record CreateProductDto(
string Name,
string Description,
decimal Price,
int StockQuantity,
string Category);
// DTOs/UpdateProductDto.cs — 更新時的輸入
public record UpdateProductDto(
string? Name,
string? Description,
decimal? Price,
int? StockQuantity,
string? Category);
// Profiles/ProductProfile.cs — AutoMapper 映射設定
public class ProductProfile : Profile
{
public ProductProfile()
{
CreateMap<Product, ProductDto>();
CreateMap<CreateProductDto, Product>();
CreateMap<UpdateProductDto, Product>()
.ForAllMembers(opt => opt.Condition((src, dest, srcMember) =>
srcMember != null)); // 只更新非 null 的欄位
}
}
📌 健康檢查 (Health Checks)
微服務必須提供健康檢查端點,讓 API Gateway 和 Kubernetes 知道服務狀態。
// Program.cs
builder.Services.AddHealthChecks()
.AddNpgSql(connectionString, name: "database")
.AddCheck("self", () => HealthCheckResult.Healthy());
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json";
var result = new
{
status = report.Status.ToString(),
checks = report.Entries.Select(e => new
{
name = e.Key,
status = e.Value.Status.ToString(),
duration = e.Value.Duration.TotalMilliseconds
}),
totalDuration = report.TotalDuration.TotalMilliseconds
};
await context.Response.WriteAsJsonAsync(result);
}
});
// /health/ready — 完整就緒檢查(包含資料庫)
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
// /health/live — 存活檢查(只檢查應用本身)
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false // 不執行任何外部檢查
});
📌 Swagger / OpenAPI 文件
// Program.cs
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Product Service API",
Version = "v1",
Description = "電商微服務 - 商品管理 API"
});
});
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Product Service v1");
c.RoutePrefix = string.Empty; // Swagger 作為首頁
});
📌 完整範例:Product Service
// Program.cs — 完整的 Product 微服務
var builder = WebApplication.CreateBuilder(args);
// 服務注冊
builder.Services.AddDbContext<ProductDbContext>(opt =>
opt.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddAutoMapper(typeof(Program));
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddHealthChecks()
.AddNpgSql(builder.Configuration.GetConnectionString("DefaultConnection")!);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
// ── API 端點 ──
var products = app.MapGroup("/api/products");
products.MapGet("/", async (IProductRepository repo, IMapper mapper) =>
{
var items = await repo.GetAllAsync();
return Results.Ok(mapper.Map<List<ProductDto>>(items));
});
products.MapGet("/{id}", async (int id, IProductRepository repo, IMapper mapper) =>
{
var product = await repo.GetByIdAsync(id);
return product is null ? Results.NotFound() : Results.Ok(mapper.Map<ProductDto>(product));
});
products.MapPost("/", async (CreateProductDto dto, IProductRepository repo, IMapper mapper) =>
{
var product = mapper.Map<Product>(dto);
await repo.AddAsync(product);
var result = mapper.Map<ProductDto>(product);
return Results.Created($"/api/products/{product.Id}", result);
});
app.MapHealthChecks("/health");
app.Run();
下一章: 我們將學習微服務之間如何溝通 — 同步的 HTTP/gRPC 和非同步的訊息佇列。