인터페이스는 현지화되어 있으며, Skill 소개와 설명 문서는 선택한 언어로 AI가 자동 번역합니다. 코드·명령어·경로·전문 용어는 원문을 유지하며 원작자 문서가 우선합니다.
Skills 둘러보기
개발 및 엔지니어링 · SKILL.md

spring-hateoas

spring-hateoas

Spring HATEOAS for building hypermedia-driven RESTful APIs. Covers EntityModel, CollectionModel, RepresentationModelAssembler, HAL, and affordances. USE WHEN: user mentions "HATEOAS", "hypermedia", "HAL", "EntityModel", "CollectionModel", "RepresentationModelAssembler", "Richardson Maturity Level 3" DO NOT USE FOR: simple REST APIs without hypermedia - us…

Independent GitHub snapshot · Automated classification · Original documentation governs

Skill 문서

GitHub

name: spring-hateoas description: | Spring HATEOAS for building hypermedia-driven RESTful APIs. Covers EntityModel, CollectionModel, RepresentationModelAssembler, HAL, and affordances.

USE WHEN: user mentions "HATEOAS", "hypermedia", "HAL", "EntityModel", "CollectionModel", "RepresentationModelAssembler", "Richardson Maturity Level 3"

DO NOT USE FOR: simple REST APIs without hypermedia - use plain Spring MVC, GraphQL - use spring-graphql skill allowed-tools: Read, Grep, Glob, Write, Edit

Spring HATEOAS - Quick Reference

Full Reference: See advanced.md for affordances (HAL-FORMS), embedded resources, link relations, media types, and testing patterns.

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: spring-hateoas for comprehensive documentation.

Dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>

Core Concepts

Richardson Maturity Model:
Level 0: Plain Old XML/JSON (single endpoint)
Level 1: Resources (multiple endpoints)
Level 2: HTTP Verbs (GET, POST, PUT, DELETE)
Level 3: Hypermedia Controls (HATEOAS) ← This!

EntityModel Wrapper

@GetMapping("/users/{id}")
public EntityModel<User> getUser(@PathVariable Long id) {
    User user = userService.findById(id);

    return EntityModel.of(user,
        linkTo(methodOn(UserController.class).getUser(id)).withSelfRel(),
        linkTo(methodOn(UserController.class).getAllUsers()).withRel("users"),
        linkTo(methodOn(OrderController.class).getOrdersByUser(id)).withRel("orders")
    );
}

Response Format (HAL)

{
  "id": 1,
  "name": "John Doe",
  "_links": {
    "self": { "href": "http://localhost:8080/api/users/1" },
    "users": { "href": "http://localhost:8080/api/users" },
    "orders": { "href": "http://localhost:8080/api/users/1/orders" }
  }
}

CollectionModel

@GetMapping("/users")
public CollectionModel<EntityModel<User>> getAllUsers() {
    List<User> users = userService.findAll();

    List<EntityModel<User>> userModels = users.stream()
        .map(user -> EntityModel.of(user,
            linkTo(methodOn(UserController.class).getUser(user.getId())).withSelfRel()
        ))
        .toList();

    return CollectionModel.of(userModels,
        linkTo(methodOn(UserController.class).getAllUsers()).withSelfRel()
    );
}

RepresentationModelAssembler

@Component
public class UserModelAssembler implements RepresentationModelAssembler<User, EntityModel<User>> {

    @Override
    public EntityModel<User> toModel(User user) {
        return EntityModel.of(user,
            linkTo(methodOn(UserController.class).getUser(user.getId())).withSelfRel(),
            linkTo(methodOn(UserController.class).getAllUsers()).withRel("users")
        );
    }
}

// Usage
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;
    private final UserModelAssembler assembler;

    @GetMapping("/{id}")
    public EntityModel<User> getUser(@PathVariable Long id) {
        return assembler.toModel(userService.findById(id));
    }

    @GetMapping
    public CollectionModel<EntityModel<User>> getAllUsers() {
        return assembler.toCollectionModel(userService.findAll());
    }
}

Pagination Support

@GetMapping("/users")
public PagedModel<EntityModel<User>> getAllUsers(
        @PageableDefault(size = 20) Pageable pageable,
        PagedResourcesAssembler<User> pagedAssembler) {

    Page<User> users = userService.findAll(pageable);
    return pagedAssembler.toModel(users, userModelAssembler);
}

Best Practices

DoDon't
Use ModelAssembler patternBuild links inline everywhere
Include self links alwaysOmit navigation links
Use standard IANA relationsInvent new relation names
Add conditional links for actionsShow all links regardless of state

When NOT to Use This Skill

  • Simple REST APIs - If clients don't need hypermedia navigation
  • GraphQL APIs - Use spring-graphql instead
  • Internal microservices - Often unnecessary overhead

Anti-Patterns

Anti-PatternProblemSolution
Building links inlineDuplicated code everywhereUse RepresentationModelAssembler
Missing self linksClients can't identify resourcesAlways add withSelfRel()
Hardcoded URLsBreaks on deploymentUse linkTo/methodOn builders
Inventing relationsNon-standard, hard to understandUse IANA link relations

Quick Troubleshooting

ProblemDiagnosticFix
Links not serializedCheck Accept headerEnsure HAL media type
NullPointerException in linkToCheck controller methodVerify method signature matches
Wrong base URLCheck proxy configConfigure server.forward-headers-strategy
Pagination links missingCheck assemblerUse PagedResourcesAssembler

Production Checklist

  • ModelAssemblers for all resources
  • Self links on every resource
  • Collection links from items
  • Pagination with navigation links
  • Conditional action links
  • IANA link relations where possible

Reference Documentation

저장한 Skills