使用 Apache Camel 配置 GraphQL/REST API

REST
Remote
0
02:24 AM · Dec 01 ,2025

1. 簡介

在本教程中,我們將使用 Apache Camel 構建一個小型應用程序,以公開 GraphQL 和 REST API。 Apache Camel 是一種強大的集成框架,它簡化了連接不同系統,包括 API、數據庫和消息服務。

2. 項目設置

首先,在我們的 pom.xml 文件中,我們添加了 Camel coreJettyGraphQLJackson 的依賴項:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-core</artifactId>
    <version>4.11.0</version>
</dependency>
<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java</artifactId>
    <version>23.1</version>
</dependency>
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-jetty</artifactId>
    <version>4.11.0</version>
</dependency>
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-graphql</artifactId>
    <version>4.11.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.18.0</version>
</dependency>

這些依賴項提供了構建應用程序所需的組件。 camel-jetty 允許我們使用 Jetty 作為嵌入式 Web 服務器來暴露 HTTP 端點,而 camel-graphql 用於服務 GraphQL 查詢。

最後,Jackson 用於處理 JSON 序列化和反序列化。

3. 創建 Book 模型我們將創建一個簡單的模型類,名為 Book。這個類代表我們希望在 API 中返回的數據:

public class Book {
    private String id;
    private String title;
    private String author;

    // 構造函數、getter 和 setter 方法
}

4. 創建服務類

接下來,我們創建一個 BookService 類,該類返回書籍列表。為了簡化,我們模擬數據:

public class BookService {
    private final List<Book> books = new ArrayList<>();

    public BookService() {
        books.add(new Book("1", "Clean Code", "Robert"));
        books.add(new Book("2", "Effective Java", "Joshua"));
    }

    public List<Book> getBooks() {
        return books;
    }

    public Book getBookById(String id) {
        return books.stream().filter(b -> b.getId().equals(id)).findFirst().orElse(null);
    }

    public Book addBook(Book book) {
        books.add(book);
        return book;
    }
}

此服務提供三種主要操作:檢索所有書籍、按 ID 獲取書籍以及添加新書籍。

5. 創建 REST 端點與 Camel

我們將使用 Apache Camel 與 Jetty 作為嵌入式 Web 服務器來暴露 RESTful 端點。Camel 簡化了定義 HTTP 端點和路由邏輯,通過 RouteBuilder

讓我們從在一個名為 BookRoute 的類中定義一個路由開始:

public class BookRoute extends RouteBuilder {
    private final BookService bookService = new BookService();

    @Override
    public void configure() {
        onException(Exception.class)
          .handled(true)
          .setHeader("Content-Type", constant("application/json"))
          .setBody(simple("{\"error\": \"${exception.message}\"}"));
        restConfiguration()
          .component("jetty")
          .host("localhost")
          .port(8080)
          .contextPath("/api")
          .bindingMode(RestBindingMode.json);

        //...
    }
}

configure() 方法是所有路由定義的地方。Camel 在初始化期間調用它來構建處理管道。 我們使用 onException() 方法來創建一個全局異常處理程序,它捕獲在請求處理期間拋出的任何未處理的異常。

restConfiguration() 定義服務器設置。我們使用 Jetty 在端口 8080,並將綁定模式設置為 JSON,以便 Camel 自動將 Java 對象轉換為 JSON 響應。

主機和端口設置確定我們的 API 將何處可訪問,而上下文路徑建立所有端點的基本 URL 前綴。

接下來,我們將創建三個用於管理 Book 資源的端點:

  • GET /api/books: 一個 GET 端點用於檢索所有書籍
  • GET /api/books/{id}: 一個 GET 端點用於根據 ID 檢索書籍
  • POST /api/books: 一個 POST 端點用於添加新書籍

讓我們使用 rest() 定義我們的實際 REST 端點:

rest("/books") 
  .get().to("direct:getAllBooks") 
  .get("/{id}").to("direct:getBookById")
  .post().type(Book.class).to("direct:addBook");

我們現在定義每個操作的內部 Camel 路由:

  • from(“direct:getAllBooks”): 當請求到達 /api/books 時,此路由被觸發。它調用 bookService.getBooks() 以返回書籍列表。
  • from(“direct:getBookById”): 此路由在客户端請求書籍 ID 時觸發。路徑變量 id 自動映射到 Camel 標頭 id,該標頭傳遞給 bookService.getBookById()
  • from(“direct:addBook”): 當接收到帶有 JSON 身體的 POST 請求時,Camel 將其反序列化為 Book 對象,並調用 bookService.addBook() 與它。

這些連接 direct:* 端點到 BookService 方法:

from("direct:getAllBooks")
  .bean(bookService, "getBooks");

from("direct:getBookById")
  .bean(bookService, "getBookById(${header.id})");

from("direct:addBook")
  .bean(bookService, "addBook");

通過利用 Apache Camel 的流暢 DSL,我們清晰地將 HTTP 路由與業務邏輯分開,並提供了一種可維護且可擴展的方式來暴露 REST API。

6. 創建 GraphQL 模式

為了向我們的應用程序添加 GraphQL 支持,我們首先在一個名為 books.graphqls 的單獨文件中定義模式。此文件使用 GraphQL 模式定義語言 (SDL),允許我們以一種簡單、聲明性的格式描述 API 的結構:

type Book {
  id: String!
  title: String!
  author: String
}

type Query {
  books: [Book]
  bookById(id: String!): Book
}

type Mutation {
  addBook(id: String!, title: String!, author: String): Book
}

模式從一個 Book 類型開始,它代表我們系統中的主要實體。它包含三個字段:idtitleauthorid 和 title 字段已用感嘆號 (!) 註釋,指示這些字段是不可空的。

在類型定義之後,Query 類型概述了客户端可以執行的數據檢索操作。具體來説,它允許客户端使用 books 查詢或使用 bookById 查詢檢索單個圖書。

為了允許客户端創建新的圖書條目,我們添加一個 Mutation 類型。 addBook 變體接受兩個參數——title (必需) 和 author (可選),並返回新創建的 Book 對象。

現在,我們需要創建一個將 GraphQL 查詢連接到 Java 服務的模式加載器類。我們創建一個名為 CustomSchemaLoader 的類,該類加載模式、將其解析為註冊表,並定義如何使用數據獲取器解決每個 GraphQL 操作:

public class CustomSchemaLoader{
    private final BookService bookService = new BookService();

    public GraphQLSchema loadSchema() {
        try (InputStream schemaStream = getClass().getClassLoader().getResourceAsStream("books.graphqls")) {
            if (schemaStream == null) {
                throw new RuntimeException("GraphQL schema file 'books.graphqls' not found in classpath");
            }

            TypeDefinitionRegistry registry = new SchemaParser()
              .parse(new InputStreamReader(schemaStream));

            RuntimeWiring wiring = buildRuntimeWiring();
            return new SchemaGenerator().makeExecutableSchema(registry, wiring);

        } catch (Exception e) {
            logger.error("Failed to load GraphQL schema", e);
            throw new RuntimeException("GraphQL schema initialization failed", e);
        }
    }

    public RuntimeWiring buildRuntimeWiring() {
        return RuntimeWiring.newRuntimeWiring()
          .type("Query", builder -> builder
            .dataFetcher("books", env -> bookService.getBooks())
            .dataFetcher("bookById", env -> bookService.getBookById(env.getArgument("id"))))
          .type("Mutation", builder -> builder
            .dataFetcher("addBook", env -> {
                String id = env.getArgument("id");
                String title = env.getArgument("title");
                String author = env.getArgument("author");
                if (title == null || title.isEmpty()) {
                    throw new IllegalArgumentException("Title cannot be empty");
                }
                return bookService.addBook(new Book(id, title, author));
          }))
          .build();
    }
}

dataFetcher() 作為 GraphQL 查詢或變體和我們服務層中實際方法之間的連接器。 例如,當客户端查詢 books 時,系統內部調用 bookService.getBooks()

對於 addBook 變體,解析器提取 titleauthor 參數,並將它們傳遞給 bookService.addBook(title, author)

7. 添加 GraphQL 路由

有了我們的 schema 和服務邏輯,下一步是使用 Apache Camel 暴露我們的 GraphQL 端點。 為了實現這一點,我們定義一條 Camel 路由,該路由監聽傳入的 HTTP POST 請求並將它們委託給 GraphQL 引擎進行處理。

我們使用 Jetty 組件配置路由,監聽 HTTP 請求,端口為 8080,具體路徑為 /graphql:

from("jetty:http://localhost:8088/graphql?matchOnUriPrefix=true")
    .log("Received GraphQL request: ${body}")
    .convertBodyTo(String.class)
    .process(exchange -> {
        String body = exchange.getIn().getBody(String.class);
        try {
            Map<String, Object> payload = new ObjectMapper().readValue(body, Map.class);
            String query = (String) payload.get("query");
            if (query == null || query.trim().isEmpty()) {
                throw new IllegalArgumentException("Missing 'query' field in request body");
            }
            ExecutionInput executionInput = ExecutionInput.newExecutionInput()
              .query(query)
              .build();
            ExecutionResult result = graphQL.execute(executionInput);
            Map<String, Object> response = result.toSpecification();
            exchange.getIn().setBody(response);
        } catch (Exception e) {
            throw new RuntimeException("GraphQL processing error", e);
        }
    })
    .marshal().json(JsonLibrary.Jackson)
    .setHeader(Exchange.CONTENT_TYPE, constant("application/json"));

該路由監聽發送到 /graphql 的 HTTP POST 請求。 它從傳入的 payload 中提取“query”字段並將其執行到加載的 GraphQL schema 中。 查詢的結果被轉換成標準 GraphQL 響應格式,然後被 marshaled 回 JSON。

8. 主應用程序類

現在路線已定義,我們需要一個主類來啓動應用程序。此類負責初始化 Camel 文檔上下文、註冊模式加載器、添加路線以及保持服務器運行。

我們創建一個名為 CamelRestGraphQLApp 的類,其中包含一個主方法,用於執行必要的設置:

public class CamelRestGraphQLApp {
    public static void main(String[] args) throws Exception {
        CamelContext context = new DefaultCamelContext();
        context.addRoutes(new BookRoute());
        context.start();
        logger.info("服務器運行在 http://localhost:8080");
        Thread.sleep(Long.MAX_VALUE);
        context.stop();
    }
}

此類註冊模式加載器作為 Bean,添加路線並啓動服務器。

Thread.sleep(Long.MAX_VALUE) 調用是一種簡單的方法,用於保持應用程序運行。在生產級應用程序中,這將被更健壯的機制用於管理應用程序生命週期替換,但僅用於演示目的,它保持服務器運行以處理傳入請求。

9. 測試我們可以使用 Camel 的 CamelContextProducerTemplate 來模擬發送 HTTP 請求:

@Test
void whenCallingRestGetAllBooks_thenShouldReturnBookList() {
    String response = template.requestBodyAndHeader(
      "http://localhost:8080/api/books",
      null,
      Exchange.CONTENT_TYPE,
      "application/json",
      String.class
    );

    assertNotNull(response);
    assertTrue(response.contains("Clean Code"));
    assertTrue(response.contains("Effective Java"));
}

@Test
void whenCallingBooksQuery_thenShouldReturnAllBooks() {
    String query = """
    {
        "query": "{ books { id title author } }"
    }""";

    String response = template.requestBodyAndHeader(
      "http://localhost:8080/graphql",
      query,
      Exchange.CONTENT_TYPE,
      "application/json",
      String.class
    );
    assertNotNull(response);
    assertTrue(response.contains("Clean Code"));
    assertTrue(response.contains("Effective Java"));
}

10. 結論

在本文中,我們成功地將 REST 和 GraphQL 端點集成到我們的 Camel 應用程序中,從而通過查詢式和修改式 API 有效地管理圖書數據。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.