핸들러

Handler에는 메서드가 하나뿐이므로, 핸들러가 어떤 형태를 띨지는 애플리케이션이 정한다. 예제 앱은 세 가지 형태를 모두 쓴다: 람다, Action 클래스, 참조로 등록하는 public 메서드.

람다

클래스로 만들 만한 상태가 없을 때는 람다를 쓴다:

app.get("/openapi.json", req -> {
    JsonValue resBody = OpenApi.document("Flashcard API", "1.0.0", app.routes());
    return WebResponse.json(resBody);
});

Action 클래스

정확히 하나의 라우트에 답하는 핸들러에는 클래스를 쓴다. 클래스가 Handler를 구현하므로, 인스턴스를 그대로 등록한다:

public class StatsAction implements Handler {

    private final StatsService statsService;

    public StatsAction(StatsService statsService) {
        this.statsService = statsService;
    }

    @Override
    public WebResponse handle(WebRequest req) {
        return WebResponse.template("stats", Model.of("stats", statsService.overview()));
    }
}
app.get("/stats", statsAction);

Action은 ADR(Action-Domain-Responder) 패턴의 A다: 정확히 하나의 요청에 답하는 웹 계층 클래스. Laravel의 단일 액션 컨트롤러가 같은 관례를 따른다. 프레임워크는 이 이름을 알지 못한다. Handler가 인터페이스이고, …Action은 Handler를 직접 구현하는 클래스의 이름 관례다.

참조로 등록하는 public 메서드

한 클래스가 관련된 여러 라우트에 답할 때는 메서드를 참조로 등록한다:

public class DeckController {

    public WebResponse showDeck(WebRequest req) { ... }

    public WebResponse renameDeck(WebRequest req) { ... }
}
app.get("/decks/{deckId}", deckController::showDeck);
app.post("/decks/{deckId}/rename", deckController::renameDeck);

메서드가 public인 이유는 등록이 의도적으로 클래스 바깥에서 일어나기 때문이다. Controller 인터페이스도 없고 구현해야 할 register(App) 메서드도 없다. 따라서 애플리케이션이 답하는 모든 라우트는 한 목록 안의 문장 하나이며, app.routes()는 거기 쓰인 것을 정확히 보고한다. 자기 라우트를 스스로 등록하는 클래스는 라우팅 테이블의 절반을 자기 안에 숨기며, 애너테이션 스캔이 하는 일을 손으로 옮겨 적은 셈이다.