2. 第一个ASP.NET Core微服务

上一节我们明确了微服务的边界与拆分思路,这一节要把概念落到代码上:用 ASP.NET Core Web API 模板创建一个真正能跑起来的最小服务。所谓“最小可运行”,指的是它具备一个微服务最基本的骨架——独立进程、独立端口、可被 HTTP 调用的接口、可观测的接口文档,以及可配置的启动行为。我们以“商品目录服务(Catalog Service)”为例,它只负责商品信息的查询与新增,边界清晰,适合作为第一个微服务。

创建项目与理解项目结构

在命令行中执行以下命令即可生成一个 Web API 项目:

dotnet new webapi -n Catalog.Service -o src/Catalog.Service
cd src/Catalog.Service
dotnet run

模板生成的项目结构并不复杂,但每一部分都对应微服务的运行要素。Program.cs 是应用入口,负责构建主机、注册服务、配置中间件与路由;appsettings.json 与 appsettings.Development.json 承载配置,前者是基础配置,后者在开发环境下覆盖;Properties/launchSettings.json 定义本地启动配置,包括应用 URL、环境变量与启动配置文件;Controllers 目录存放控制器,是路由与业务入口的落点。理解这套结构的关键在于:ASP.NET Core 采用“主机 + 中间件管道 + 依赖注入”的统一模型,微服务只是这个模型的一个独立实例。

需要特别说明的是,模板默认可能生成最小 API(在 Program.cs 中直接写 app.MapGet),也可能生成控制器风格。本课程统一采用控制器风格,因为它更利于按资源组织接口,也便于后续引入过滤器、模型验证与版本控制。若模板生成的是最小 API,可手动添加 Controllers 目录并注册控制器服务。

启动配置:端口、环境与配置文件

微服务必须拥有独立且稳定的监听地址。launchSettings.json 中的 applicationUrl 决定本地调试端口,例如设置为 http://localhost:5101。生产环境则通过环境变量 ASPNETCORE_URLS 或反向代理统一入口。环境变量 ASPNETCORE_ENVIRONMENT 控制加载哪个配置文件,开发环境为 Development,生产为 Production。这种“配置分层 + 环境覆盖”的机制,使同一个镜像可以在不同环境以不同参数运行,是微服务可移植性的基础。

在 Program.cs 中,服务注册与中间件顺序决定了请求如何被处理。典型的最小配置如下:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

这里有几个关键点:AddControllers 注册控制器相关服务;AddEndpointsApiExplorer 与 AddSwaggerGen 为接口文档提供元数据;UseSwagger 与 UseSwaggerUI 仅在开发环境启用,避免生产环境暴露接口细节;MapControllers 将控制器动作映射为路由端点。中间件顺序不能随意调整,例如授权必须在路由之前,否则无法正确拦截请求。

路由与控制器:把资源暴露为 HTTP 接口

控制器是路由的载体。我们创建一个 ProductsController,用特性路由声明资源路径:

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private static readonly List<Product> _products = new()
    {
        new Product { Id = 1, Name = "机械键盘", Price = 399.00m },
        new Product { Id = 2, Name = "无线鼠标", Price = 129.00m }
    };

    [HttpGet]
    public ActionResult<IEnumerable<Product>> GetAll() => _products;

    [HttpGet("{id:int}")]
    public ActionResult<Product> GetById(int id)
    {
        var product = _products.FirstOrDefault(p => p.Id == id);
        return product is null ? NotFound() : product;
    }

    [HttpPost]
    public ActionResult<Product> Create(Product product)
    {
        product.Id = _products.Max(p => p.Id) + 1;
        _products.Add(product);
        return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
    }
}

这段代码体现了 REST 风格的核心约定:GET /api/products 返回集合,GET /api/products/1 返回单个资源,POST /api/products 创建资源并返回 201 与 Location 头。[ApiController] 启用自动模型验证与参数绑定推断,[Route("api/[controller]")] 中的 [controller] 会被替换为控制器名去掉 Controller 后缀,即 products。路由参数 {id:int} 中的约束确保只有整数才能匹配,避免无效请求进入动作方法。

这里使用静态列表模拟数据,是为了让第一个服务不依赖数据库即可运行。真实微服务中,这一层会替换为仓储或数据库访问,但接口契约与路由结构保持不变,这正是微服务“边界稳定、实现可替换”的体现。

Swagger 集成:让接口可发现、可调试

Swagger(OpenAPI)为微服务提供了自描述能力。启动服务后访问 http://localhost:5101/swagger,可以看到自动生成的接口列表、请求参数、响应模型与状态码。点击 “Try it out” 可以直接发起请求,验证 GET /api/products 是否返回两条商品数据,以及 POST 是否返回 201。对于微服务架构,Swagger 不仅是调试工具,更是服务契约的可视化表达,后续网关聚合、客户端生成都依赖这份 OpenAPI 文档。

建议为 Swagger 添加 XML 注释与版本信息,使文档包含接口说明与示例值。生产环境应关闭 Swagger UI,或通过网关统一鉴权后暴露。

最小服务的运行闭环

把上述内容串起来,一个最小微服务的运行闭环是:dotnet run 启动独立进程,Kestrel 监听 5101 端口;请求进入中间件管道,经过 HTTPS 重定向与授权后到达路由;路由根据 api/products 匹配到控制器动作;动作返回数据或状态码;Swagger 从元数据生成文档供调试。判断服务是否达标,可以检查三点:进程是否独立监听端口、/swagger 是否可访问、GET /api/products 是否返回预期 JSON。

Catalog.Service 最小可运行微服务结构 项目结构 Program.cs appsettings.json launchSettings.json Controllers/ Properties/ 启动配置 ASPNETCORE_URLS http://localhost:5101 ASPNETCORE_ENVIRONMENT Development / Production Kestrel 独立监听 Swagger 集成 AddEndpointsApiExplorer AddSwaggerGen UseSwagger / UseSwaggerUI /swagger 可调试 OpenAPI 契约 请求处理链:路由 → 控制器 → 响应 HTTP 请求 GET /api/products 路由匹配 api/[controller] 控制器动作 GetAll / GetById JSON 响应 200 / 201 / 404 独立进程 · 独立端口 · 可调试接口 · 契约可发现

完成这一节后,你已经拥有一个可运行、可调试、可扩展的微服务雏形。它的价值不在于功能多少,而在于结构正确:独立部署单元、清晰的资源路由、可发现的接口契约。后续引入数据库、服务注册、网关与容器化时,都是在这个骨架上叠加能力,而不是推倒重来。