micronaut

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Micronaut Guide

Micronaut 指南

Applies to: Micronaut 4.x, Java 21+, Microservices, Serverless, CLI Applications, IoT
适用于:Micronaut 4.x、Java 21+、微服务、Serverless、CLI应用、IoT

Core Principles

核心原则

  1. Compile-Time DI: All dependency injection resolved at compile time -- no reflection at runtime
  2. GraalVM Native: First-class native image support for fast startup and low memory
  3. Reactive by Default: Built-in reactive programming with non-blocking I/O
  4. Cloud-Native: Service discovery, distributed tracing, config management out of the box
  5. Fast Startup: Sub-100ms startup makes it ideal for serverless and containers
  6. Standard Library: Use
    jakarta.inject
    annotations, not Spring-specific ones
  1. 编译时DI:所有依赖注入在编译阶段完成解析,运行时无反射操作
  2. GraalVM Native:原生镜像第一等支持,启动速度快、内存占用低
  3. 默认响应式:内置响应式编程能力,支持非阻塞I/O
  4. 云原生:开箱支持服务发现、分布式链路追踪、配置管理
  5. 快速启动:启动耗时低于100ms,非常适合Serverless和容器场景
  6. 标准库规范:使用
    jakarta.inject
    注解,而非Spring专属注解

Guardrails

护栏规范

Dependency Injection

依赖注入

  • Use constructor injection exclusively (compile-time verified)
  • Annotate beans with
    @Singleton
    ,
    @Prototype
    , or
    @RequestScope
  • Use
    @Factory
    for third-party objects that cannot be annotated
  • Use
    @Requires
    for conditional bean loading
  • Never use
    @Autowired
    or Spring DI annotations
  • Prefer interface-based injection for testability
  • Avoid field injection (not compile-time verifiable)
  • 仅使用构造函数注入(编译时可校验)
  • 为Bean标注
    @Singleton
    @Prototype
    @RequestScope
    注解
  • 对无法添加注解的第三方对象使用
    @Factory
    创建
  • 使用
    @Requires
    实现条件化Bean加载
  • 禁止使用
    @Autowired
    或Spring DI相关注解
  • 优先使用基于接口的注入提升可测试性
  • 避免使用字段注入(无法在编译时校验)

HTTP Controllers

HTTP控制器

  • Annotate controllers with
    @Controller("/path")
  • Use
    @ExecuteOn(TaskExecutors.BLOCKING)
    for JDBC/blocking operations
  • Apply
    @Validated
    at class level for request validation
  • Return
    HttpResponse<T>
    for status code control, direct type for 200 OK
  • Use
    @Status(HttpStatus.CREATED)
    for POST endpoints
  • Keep controllers thin -- delegate business logic to services
  • Add OpenAPI annotations (
    @Operation
    ,
    @ApiResponse
    ) for all endpoints
  • 为控制器标注
    @Controller("/path")
    注解
  • 对JDBC/阻塞操作添加
    @ExecuteOn(TaskExecutors.BLOCKING)
    注解
  • 在类级别添加
    @Validated
    注解实现请求参数校验
  • 需自定义状态码时返回
    HttpResponse<T>
    ,200 OK场景可直接返回对应类型
  • 为POST接口添加
    @Status(HttpStatus.CREATED)
    注解
  • 保持控制器逻辑精简,将业务逻辑下沉到Service层
  • 为所有接口添加OpenAPI注解(
    @Operation
    @ApiResponse

Data Access

数据访问

  • Use
    @JdbcRepository
    or
    @MongoRepository
    with dialect specification
  • Extend
    PageableRepository<Entity, ID>
    for pagination support
  • Use
    @Query
    for custom queries, prefer derived query methods when possible
  • Apply
    @Transactional
    at service layer, not repository layer
  • Use
    @Transactional(readOnly = true)
    for read-only operations
  • Use Flyway or Liquibase for schema migrations (never
    schema-generate: CREATE
    )
  • Always include rollback scripts for migrations
  • 使用指定方言的
    @JdbcRepository
    @MongoRepository
  • 继承
    PageableRepository<Entity, ID>
    实现分页能力
  • 自定义查询使用
    @Query
    注解,优先使用派生查询方法
  • 在Service层而非Repository层添加
    @Transactional
    注解
  • 对只读操作使用
    @Transactional(readOnly = true)
    注解
  • 使用Flyway或Liquibase做Schema迁移(禁止使用
    schema-generate: CREATE
  • 所有迁移脚本必须配套回滚脚本

DTOs and Serialization

DTO与序列化

  • Use Java records for DTOs (immutable by default)
  • Annotate all DTOs with
    @Serdeable
    (Micronaut serialization)
  • Apply Jakarta validation annotations (
    @NotBlank
    ,
    @Email
    ,
    @Size
    )
  • Never expose domain entities directly in API responses
  • Use MapStruct with
    componentModel = "jsr330"
    for entity-DTO mapping
  • Create separate request and response DTOs
  • DTO使用Java record实现(默认不可变)
  • 所有DTO标注
    @Serdeable
    注解(Micronaut序列化专用)
  • 添加Jakarta校验注解(
    @NotBlank
    @Email
    @Size
    等)
  • 禁止在API响应中直接暴露领域实体
  • 使用
    componentModel = "jsr330
    的MapStruct实现实体与DTO的映射
  • 请求与响应DTO分开定义

Error Handling

错误处理

  • Implement
    ExceptionHandler<E, HttpResponse<T>>
    for each exception type
  • Return RFC 7807 Problem Details format for error responses
  • Use
    @Requires(classes = {...})
    on exception handlers
  • Log warnings/errors in exception handlers
  • Never expose internal details (stack traces, SQL) in error responses
  • 每类异常单独实现
    ExceptionHandler<E, HttpResponse<T>>
  • 错误响应返回RFC 7807 Problem Details格式
  • 异常处理器添加
    @Requires(classes = {...})
    注解
  • 异常处理器中记录警告/错误日志
  • 禁止在错误响应中暴露内部细节(栈轨迹、SQL等)

Configuration

配置

  • Use
    application.yml
    with environment-specific overrides (
    application-dev.yml
    )
  • Reference secrets via environment variables:
    ${ENV_VAR:default}
  • Never hardcode secrets, API keys, or passwords
  • Use
    @ConfigurationProperties
    for type-safe configuration beans
  • Enable health checks and Prometheus metrics for production
  • 使用
    application.yml
    并配套环境专属配置覆盖(如
    application-dev.yml
  • 通过环境变量引用密钥:
    ${ENV_VAR:default}
  • 禁止硬编码密钥、API Key或密码
  • 使用
    @ConfigurationProperties
    实现类型安全的配置Bean
  • 生产环境开启健康检查与Prometheus指标

Testing

测试

  • Use
    @MicronautTest
    for integration tests (starts embedded server)
  • Use
    @MockBean
    for mocking dependencies in integration tests
  • Use
    @ExtendWith(MockitoExtension.class)
    for unit tests
  • Use Testcontainers for database integration tests
  • Implement
    TestPropertyProvider
    for dynamic test configuration
  • Test names describe behavior:
    shouldCreateUserWhenEmailIsUnique
  • Coverage target: >80% for business logic, >60% overall
  • 集成测试使用
    @MicronautTest
    注解(会启动嵌入式服务)
  • 集成测试中使用
    @MockBean
    模拟依赖
  • 单元测试使用
    @ExtendWith(MockitoExtension.class)
    注解
  • 数据库集成测试使用Testcontainers
  • 实现
    TestPropertyProvider
    实现动态测试配置
  • 测试用例名称要描述行为,例如
    shouldCreateUserWhenEmailIsUnique
  • 覆盖率目标:业务逻辑>80%,整体>60%

Performance

性能

  • Use
    @ExecuteOn(TaskExecutors.BLOCKING)
    only for blocking operations
  • Use reactive types (
    Mono
    ,
    Flux
    ) for non-blocking I/O when appropriate
  • Enable AOT optimizations in
    build.gradle
    for production
  • Use connection pooling (HikariCP) with tuned pool sizes
  • Paginate all list endpoints (never return unbounded collections)
  • 仅在阻塞操作上使用
    @ExecuteOn(TaskExecutors.BLOCKING)
  • 非阻塞I/O场景按需使用响应式类型(
    Mono
    Flux
  • 生产环境在
    build.gradle
    中开启AOT优化
  • 使用连接池(HikariCP)并调整合适的连接池大小
  • 所有列表接口支持分页(禁止返回无边界集合)

Project Structure

项目结构

myapp/
├── src/main/
│   ├── java/com/example/
│   │   ├── Application.java       # Entry point with @OpenAPIDefinition
│   │   ├── config/                 # @Factory, @ConfigurationProperties
│   │   ├── controller/            # @Controller classes
│   │   ├── service/               # Business logic interfaces
│   │   │   └── impl/              # Service implementations
│   │   ├── repository/            # @JdbcRepository interfaces
│   │   ├── domain/                # @MappedEntity classes
│   │   ├── dto/                   # @Serdeable records
│   │   ├── mapper/                # MapStruct @Mapper interfaces
│   │   └── exception/             # Custom exceptions + handlers
│   └── resources/
│       ├── application.yml        # Main config (with -dev.yml, -prod.yml overrides)
│       ├── db/migration/          # Flyway SQL files
│       └── logback.xml
├── src/test/java/com/example/     # Unit + integration tests
├── build.gradle                   # Micronaut Gradle plugin
└── settings.gradle
  • service/impl/
    separates interface from implementation for testability
  • dto/
    uses
    @Serdeable
    records for API request/response types
  • mapper/
    bridges domain entities to DTOs via MapStruct
  • exception/
    bundles custom exceptions with their
    ExceptionHandler
    implementations
myapp/
├── src/main/
│   ├── java/com/example/
│   │   ├── Application.java       # Entry point with @OpenAPIDefinition
│   │   ├── config/                 # @Factory, @ConfigurationProperties
│   │   ├── controller/            # @Controller classes
│   │   ├── service/               # Business logic interfaces
│   │   │   └── impl/              # Service implementations
│   │   ├── repository/            # @JdbcRepository interfaces
│   │   ├── domain/                # @MappedEntity classes
│   │   ├── dto/                   # @Serdeable records
│   │   ├── mapper/                # MapStruct @Mapper interfaces
│   │   └── exception/             # Custom exceptions + handlers
│   └── resources/
│       ├── application.yml        # Main config (with -dev.yml, -prod.yml overrides)
│       ├── db/migration/          # Flyway SQL files
│       └── logback.xml
├── src/test/java/com/example/     # Unit + integration tests
├── build.gradle                   # Micronaut Gradle plugin
└── settings.gradle
  • service/impl/
    将接口与实现分离,提升可测试性
  • dto/
    使用
    @Serdeable
    record作为API请求/响应类型
  • mapper/
    通过MapStruct实现领域实体与DTO的转换
  • exception/
    将自定义异常与对应的
    ExceptionHandler
    实现放在一起

Compile-Time DI Patterns

编译时DI模式

Singleton Bean

单例Bean

java
@Singleton
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
    private final UserRepository userRepository;
    private final UserMapper userMapper;
    // Constructor injection -- resolved at compile time
}
java
@Singleton
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
    private final UserRepository userRepository;
    private final UserMapper userMapper;
    // Constructor injection -- resolved at compile time
}

Factory for Third-Party Objects

第三方对象工厂

java
@Factory
public class SecurityBeans {
    @Singleton
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12);
    }
}
java
@Factory
public class SecurityBeans {
    @Singleton
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12);
    }
}

Conditional Beans

条件Bean

java
@Singleton
@Requires(env = "production")
public class ProductionEmailService implements EmailService { }

@Singleton
@Requires(env = "dev")
public class MockEmailService implements EmailService { }
java
@Singleton
@Requires(env = "production")
public class ProductionEmailService implements EmailService { }

@Singleton
@Requires(env = "dev")
public class MockEmailService implements EmailService { }

Controller Pattern

控制器模式

java
@Controller("/api/users")
@Validated
@RequiredArgsConstructor
@ExecuteOn(TaskExecutors.BLOCKING)
@Secured(SecurityRule.IS_AUTHENTICATED)
@Tag(name = "Users", description = "User management endpoints")
public class UserController {

    private final UserService userService;

    @Post
    @Status(HttpStatus.CREATED)
    @Operation(summary = "Create a new user")
    @ApiResponse(responseCode = "201", description = "User created")
    @ApiResponse(responseCode = "409", description = "Email already exists")
    public HttpResponse<UserResponse> createUser(@Body @Valid UserRequest request) {
        UserResponse user = userService.createUser(request);
        return HttpResponse.created(user)
            .headers(h -> h.location(URI.create("/api/users/" + user.id())));
    }

    @Get("/{id}")
    @Operation(summary = "Get user by ID")
    public UserResponse getUserById(@PathVariable Long id) {
        return userService.getUserById(id);
    }

    @Get
    @Operation(summary = "List users with pagination")
    public PageResponse<UserResponse> getAllUsers(
            @QueryValue(defaultValue = "0") int page,
            @QueryValue(defaultValue = "20") int size) {
        return userService.getAllUsers(page, size);
    }

    @Put("/{id}")
    public UserResponse updateUser(@PathVariable Long id, @Body @Valid UserRequest request) {
        return userService.updateUser(id, request);
    }

    @Delete("/{id}")
    @Status(HttpStatus.NO_CONTENT)
    @Secured({"ROLE_ADMIN"})
    public void deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
    }
}
java
@Controller("/api/users")
@Validated
@RequiredArgsConstructor
@ExecuteOn(TaskExecutors.BLOCKING)
@Secured(SecurityRule.IS_AUTHENTICATED)
@Tag(name = "Users", description = "User management endpoints")
public class UserController {

    private final UserService userService;

    @Post
    @Status(HttpStatus.CREATED)
    @Operation(summary = "Create a new user")
    @ApiResponse(responseCode = "201", description = "User created")
    @ApiResponse(responseCode = "409", description = "Email already exists")
    public HttpResponse<UserResponse> createUser(@Body @Valid UserRequest request) {
        UserResponse user = userService.createUser(request);
        return HttpResponse.created(user)
            .headers(h -> h.location(URI.create("/api/users/" + user.id())));
    }

    @Get("/{id}")
    @Operation(summary = "Get user by ID")
    public UserResponse getUserById(@PathVariable Long id) {
        return userService.getUserById(id);
    }

    @Get
    @Operation(summary = "List users with pagination")
    public PageResponse<UserResponse> getAllUsers(
            @QueryValue(defaultValue = "0") int page,
            @QueryValue(defaultValue = "20") int size) {
        return userService.getAllUsers(page, size);
    }

    @Put("/{id}")
    public UserResponse updateUser(@PathVariable Long id, @Body @Valid UserRequest request) {
        return userService.updateUser(id, request);
    }

    @Delete("/{id}")
    @Status(HttpStatus.NO_CONTENT)
    @Secured({"ROLE_ADMIN"})
    public void deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
    }
}

Data Access Patterns

数据访问模式

Domain Entity

领域实体

java
@Serdeable
@MappedEntity("users")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
    @Id
    @GeneratedValue(GeneratedValue.Type.AUTO)
    private Long id;

    @Column("email")
    private String email;

    @Column("password")
    private String password;

    @DateCreated
    @Column("created_at")
    private Instant createdAt;

    @DateUpdated
    @Column("updated_at")
    private Instant updatedAt;
}
java
@Serdeable
@MappedEntity("users")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
    @Id
    @GeneratedValue(GeneratedValue.Type.AUTO)
    private Long id;

    @Column("email")
    private String email;

    @Column("password")
    private String password;

    @DateCreated
    @Column("created_at")
    private Instant createdAt;

    @DateUpdated
    @Column("updated_at")
    private Instant updatedAt;
}

Repository

Repository

java
@JdbcRepository(dialect = Dialect.POSTGRES)
public interface UserRepository extends PageableRepository<User, Long> {

    Optional<User> findByEmail(String email);

    boolean existsByEmail(String email);

    List<User> findByActiveTrue();

    Page<User> findByRole(String role, Pageable pageable);

    @Query("UPDATE User u SET u.active = :active WHERE u.id = :id")
    void updateActiveStatus(Long id, boolean active);
}
java
@JdbcRepository(dialect = Dialect.POSTGRES)
public interface UserRepository extends PageableRepository<User, Long> {

    Optional<User> findByEmail(String email);

    boolean existsByEmail(String email);

    List<User> findByActiveTrue();

    Page<User> findByRole(String role, Pageable pageable);

    @Query("UPDATE User u SET u.active = :active WHERE u.id = :id")
    void updateActiveStatus(Long id, boolean active);
}

DTO Records

DTO记录类

java
@Serdeable
public record UserRequest(
    @NotBlank @Email @Size(max = 255) String email,
    @NotBlank @Size(min = 8, max = 100) String password,
    @NotBlank @Size(max = 100) String firstName,
    @NotBlank @Size(max = 100) String lastName
) {}

@Serdeable
public record UserResponse(
    Long id, String email, String firstName, String lastName,
    Boolean active, Instant createdAt, Instant updatedAt
) {}

@Serdeable
public record PageResponse<T>(
    List<T> content, int page, int size,
    long totalElements, int totalPages, boolean first, boolean last
) {
    public static <T> PageResponse<T> of(List<T> content, int page, int size, long total) {
        int totalPages = (int) Math.ceil((double) total / size);
        return new PageResponse<>(content, page, size, total, totalPages,
            page == 0, page >= totalPages - 1);
    }
}
java
@Serdeable
public record UserRequest(
    @NotBlank @Email @Size(max = 255) String email,
    @NotBlank @Size(min = 8, max = 100) String password,
    @NotBlank @Size(max = 100) String firstName,
    @NotBlank @Size(max = 100) String lastName
) {}

@Serdeable
public record UserResponse(
    Long id, String email, String firstName, String lastName,
    Boolean active, Instant createdAt, Instant updatedAt
) {}

@Serdeable
public record PageResponse<T>(
    List<T> content, int page, int size,
    long totalElements, int totalPages, boolean first, boolean last
) {
    public static <T> PageResponse<T> of(List<T> content, int page, int size, long total) {
        int totalPages = (int) Math.ceil((double) total / size);
        return new PageResponse<>(content, page, size, total, totalPages,
            page == 0, page >= totalPages - 1);
    }
}

Configuration (application.yml)

配置(application.yml)

yaml
micronaut:
  application:
    name: myapp
  server:
    port: 8080
  security:
    authentication: bearer
    token:
      jwt:
        signatures:
          secret:
            generator:
              secret: ${JWT_SECRET}
              jws-algorithm: HS256
    intercept-url-map:
      - pattern: /health/**
        access: [isAnonymous()]
      - pattern: /api/**
        access: [isAuthenticated()]

datasources:
  default:
    url: jdbc:postgresql://localhost:5432/myapp
    username: ${DB_USERNAME:postgres}
    password: ${DB_PASSWORD:postgres}

flyway:
  datasources:
    default:
      enabled: true
      locations: classpath:db/migration

endpoints:
  health: { enabled: true, sensitive: false }
  prometheus: { enabled: true, sensitive: false }
yaml
micronaut:
  application:
    name: myapp
  server:
    port: 8080
  security:
    authentication: bearer
    token:
      jwt:
        signatures:
          secret:
            generator:
              secret: ${JWT_SECRET}
              jws-algorithm: HS256
    intercept-url-map:
      - pattern: /health/**
        access: [isAnonymous()]
      - pattern: /api/**
        access: [isAuthenticated()]

datasources:
  default:
    url: jdbc:postgresql://localhost:5432/myapp
    username: ${DB_USERNAME:postgres}
    password: ${DB_PASSWORD:postgres}

flyway:
  datasources:
    default:
      enabled: true
      locations: classpath:db/migration

endpoints:
  health: { enabled: true, sensitive: false }
  prometheus: { enabled: true, sensitive: false }

Error Handling Pattern

错误处理模式

Custom Exception

自定义异常

java
public class ResourceNotFoundException extends RuntimeException {
    private final String resourceName;
    private final String fieldName;
    private final Object fieldValue;

    public ResourceNotFoundException(String resource, String field, Object value) {
        super(String.format("%s not found with %s: '%s'", resource, field, value));
        this.resourceName = resource;
        this.fieldName = field;
        this.fieldValue = value;
    }
    // getters
}
java
public class ResourceNotFoundException extends RuntimeException {
    private final String resourceName;
    private final String fieldName;
    private final Object fieldValue;

    public ResourceNotFoundException(String resource, String field, Object value) {
        super(String.format("%s not found with %s: '%s'", resource, field, value));
        this.resourceName = resource;
        this.fieldName = field;
        this.fieldValue = value;
    }
    // getters
}

Exception Handler

异常处理器

java
@Singleton
@Produces
@Requires(classes = {ResourceNotFoundException.class, ExceptionHandler.class})
public class ResourceNotFoundExceptionHandler
    implements ExceptionHandler<ResourceNotFoundException, HttpResponse<ErrorResponse>> {

    @Override
    public HttpResponse<ErrorResponse> handle(HttpRequest request, ResourceNotFoundException ex) {
        ErrorResponse error = ErrorResponse.of(404, "Not Found", ex.getMessage(), request.getPath());
        return HttpResponse.notFound(error);
    }
}
java
@Singleton
@Produces
@Requires(classes = {ResourceNotFoundException.class, ExceptionHandler.class})
public class ResourceNotFoundExceptionHandler
    implements ExceptionHandler<ResourceNotFoundException, HttpResponse<ErrorResponse>> {

    @Override
    public HttpResponse<ErrorResponse> handle(HttpRequest request, ResourceNotFoundException ex) {
        ErrorResponse error = ErrorResponse.of(404, "Not Found", ex.getMessage(), request.getPath());
        return HttpResponse.notFound(error);
    }
}

Testing Patterns

测试模式

Unit Test (Mockito)

单元测试(Mockito)

java
@ExtendWith(MockitoExtension.class)
@DisplayName("UserService")
class UserServiceTest {

    @Mock private UserRepository userRepository;
    @Mock private UserMapper userMapper;
    private UserServiceImpl userService;

    @BeforeEach
    void setUp() {
        userService = new UserServiceImpl(userRepository, userMapper);
    }

    @Test
    @DisplayName("should create user when email is unique")
    void shouldCreateUserWhenEmailIsUnique() {
        when(userRepository.existsByEmail("test@example.com")).thenReturn(false);
        when(userMapper.toEntity(any())).thenReturn(new User());
        when(userRepository.save(any())).thenReturn(new User());
        when(userMapper.toResponse(any())).thenReturn(expectedResponse);

        UserResponse result = userService.createUser(request);

        assertThat(result).isNotNull();
        verify(userRepository).save(any());
    }
}
java
@ExtendWith(MockitoExtension.class)
@DisplayName("UserService")
class UserServiceTest {

    @Mock private UserRepository userRepository;
    @Mock private UserMapper userMapper;
    private UserServiceImpl userService;

    @BeforeEach
    void setUp() {
        userService = new UserServiceImpl(userRepository, userMapper);
    }

    @Test
    @DisplayName("should create user when email is unique")
    void shouldCreateUserWhenEmailIsUnique() {
        when(userRepository.existsByEmail("test@example.com")).thenReturn(false);
        when(userMapper.toEntity(any())).thenReturn(new User());
        when(userRepository.save(any())).thenReturn(new User());
        when(userMapper.toResponse(any())).thenReturn(expectedResponse);

        UserResponse result = userService.createUser(request);

        assertThat(result).isNotNull();
        verify(userRepository).save(any());
    }
}

Integration Test (MicronautTest + Testcontainers)

集成测试(MicronautTest + Testcontainers)

java
@MicronautTest
@Testcontainers
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class IntegrationTest implements TestPropertyProvider {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine");

    @Inject @Client("/") HttpClient client;

    @Override
    public Map<String, String> getProperties() {
        postgres.start();
        return Map.of(
            "datasources.default.url", postgres.getJdbcUrl(),
            "datasources.default.username", postgres.getUsername(),
            "datasources.default.password", postgres.getPassword()
        );
    }

    @Test
    void healthEndpoint_shouldReturnUp() {
        var response = client.toBlocking().exchange(HttpRequest.GET("/health"), String.class);
        assertThat(response.status().getCode()).isEqualTo(200);
    }
}
java
@MicronautTest
@Testcontainers
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class IntegrationTest implements TestPropertyProvider {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine");

    @Inject @Client("/") HttpClient client;

    @Override
    public Map<String, String> getProperties() {
        postgres.start();
        return Map.of(
            "datasources.default.url", postgres.getJdbcUrl(),
            "datasources.default.username", postgres.getUsername(),
            "datasources.default.password", postgres.getPassword()
        );
    }

    @Test
    void healthEndpoint_shouldReturnUp() {
        var response = client.toBlocking().exchange(HttpRequest.GET("/health"), String.class);
        assertThat(response.status().getCode()).isEqualTo(200);
    }
}

Build & Run Commands

构建与运行命令

bash
./gradlew build                            # Build
MICRONAUT_ENVIRONMENTS=dev ./gradlew run   # Run (dev profile)
./gradlew test                             # Run tests
./gradlew test jacocoTestReport            # Tests + coverage report
./gradlew nativeCompile                    # GraalVM native image
./gradlew dockerBuild                      # Docker image
./gradlew dockerBuildNative                # Native Docker image
./gradlew clean build                      # Clean build
./gradlew check                            # Lint (checkstyle/spotbugs)
bash
./gradlew build                            # Build
MICRONAUT_ENVIRONMENTS=dev ./gradlew run   # Run (dev profile)
./gradlew test                             # Run tests
./gradlew test jacocoTestReport            # Tests + coverage report
./gradlew nativeCompile                    # GraalVM native image
./gradlew dockerBuild                      # Docker image
./gradlew dockerBuildNative                # Native Docker image
./gradlew clean build                      # Clean build
./gradlew check                            # Lint (checkstyle/spotbugs)

Do's and Don'ts

最佳实践与禁忌

Do

推荐做法

  • Use constructor injection (compile-time verified)
  • Use
    @Serdeable
    for all DTOs
  • Use
    @ExecuteOn(TaskExecutors.BLOCKING)
    for blocking operations
  • Use
    @Transactional
    at the service layer
  • Use validation annotations on DTOs
  • Use native compilation for production deployments
  • Use Micronaut Data for repositories
  • Enable health checks and metrics
  • 使用构造函数注入(编译时可校验)
  • 所有DTO添加
    @Serdeable
    注解
  • 阻塞操作添加
    @ExecuteOn(TaskExecutors.BLOCKING)
    注解
  • Service层添加
    @Transactional
    注解
  • DTO上添加校验注解
  • 生产部署使用原生编译
  • Repository层使用Micronaut Data
  • 开启健康检查与指标监控

Don't

禁止做法

  • Don't use reflection-based libraries without GraalVM configuration
  • Don't use
    @Autowired
    or Spring-specific annotations
  • Don't block in reactive streams
  • Don't expose internal details in error responses
  • Don't skip DTO validation
  • Don't use mutable DTOs (prefer records)
  • Don't ignore compile-time warnings from annotation processors
  • 没有配置GraalVM的情况下使用基于反射的库
  • 使用
    @Autowired
    或Spring专属注解
  • 在响应式流中执行阻塞操作
  • 错误响应中暴露内部细节
  • 跳过DTO校验
  • 使用可变DTO(优先使用record)
  • 忽略注解处理器的编译时警告

Advanced Topics

进阶主题

For detailed patterns and examples, see:
  • references/patterns.md -- GraalVM native, messaging, security, advanced testing, deployment patterns
了解更多详细模式与示例请查看:
  • references/patterns.md -- GraalVM原生镜像、消息队列、安全、高级测试、部署模式

External References

外部参考