• 22.08.2026 23:10:41
  • Admin Admin

Spring Boot uygulamalarında OpenAPI sözleşmesini CI'da diff ile doğrulamayı, ProblemDetail hata şemalarını sürümlemeyi ve consumer contract testleriyle kırıcı değişiklikleri dağıtımdan önce yakalamayı inceler. java backend geliştirme ekipleri için uygulanabilir bir akış sunar.

Spring Boot'ta API Sözleşmesi: OpenAPI Diff ve Hata Evrimi Rehberi

Spring Framework ile OpenAPI sözleşmesini derlenebilir artefakt yapın

Bir REST API'nin Java kodundan üretilen OpenAPI belgesi, sadece dokümantasyon değil CI tarafından doğrulanacak bir teslimat artefaktı olmalıdır. spring framework tabanlı bir serviste springdoc-openapi ile belgeyi build aşamasında sabit bir dosyaya yazın; runtime'da endpoint'ten çekilen belge, aktif profil veya yanlış environment variable nedeniyle deterministik olmayabilir. Aşağıdaki Maven görevi, uygulamayı integration-test aşamasında başlatıp OpenAPI dosyasını target/openapi.json konumuna üretir.

<plugin>
  <groupId>org.springdoc</groupId>
  <artifactId>springdoc-openapi-maven-plugin</artifactId>
  <executions>
    <execution>
      <phase>integration-test</phase>
      <goals><goal>generate</goal></goals>
      <configuration>
        <apiDocsUrl>http://localhost:8080/v3/api-docs</apiDocsUrl>
        <outputFileName>openapi.json</outputFileName>
        <outputDir>${project.build.directory}</outputDir>
      </configuration>
    </execution>
  </executions>
</plugin>

Controller imzalarına dönüş tipi, HTTP durumu ve hata cevabını açıkça bağlayın. Özellikle ResponseEntity<Void> gibi gövdesiz dönüşler istemci üreticilerinde belirsiz response şeması oluşturabilir. Aşağıdaki tanımda 404 cevabının ProblemDetail olduğu sözleşmeye işlenir; istemci tarafı type alanına göre dallanabilir. Bu disiplin, java eğitimi veya java programlama eğitimi sırasında çoğu zaman atlanan fakat üretimde istemci uyumluluğunu belirleyen detaylardan biridir.

@GetMapping("/orders/{id}")
@Operation(summary = "Find order by id")
@ApiResponses({
    @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = OrderResponse.class))),
    @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(implementation = ProblemDetail.class)))
})
public OrderResponse find(@PathVariable UUID id) {
    return orderService.find(id);
}

Belgeyi commit etmek yerine CI artefaktı olarak saklayın ve release branch'in son başarılı belgesiyle kıyaslayın. Böylece source code değişikliği ile API yüzeyi değişikliğini birbirinden ayırırsınız. Bir java kursu projesinde bile target/openapi.json dosyasını build çıktısı olarak indirilebilir tutmak, frontend veya mobil ekiplerin tahmine dayalı entegrasyon yapmasını engeller.

Spring Boot eğitiminde eksik kalan konu: kırıcı OpenAPI diff kuralı

OpenAPI diff kontrolünde sadece endpoint silinmesini aramak yetersizdir. Required bir request alanının eklenmesi, enum değerinin kaldırılması, response alanının nullable durumunun değişmesi ve 2xx durum kodunun kaldırılması da istemciyi kırabilir. openapi-diff aracını Docker ile iki spec üzerinde çalıştırıp exit code'u CI kuralına bağlayın.

docker run --rm   -v "$PWD:/spec"   openapitools/openapi-diff:latest   /spec/baseline/openapi.json /spec/target/openapi.json   --fail-on-incompatible

GitHub Actions, GitLab CI veya Jenkins'te baseline dosyasını son production tag'inden indirin. Sadece main branch'teki dosyayla karşılaştırmak, aynı branch üzerinde iki ardışık kırıcı değişikliğin birbirini maskelemesine neden olur. CI adımını şu kuralla yönetin: uyumsuz değişiklik varsa merge başarısız olsun; uyumlu yeni alan varsa HTML raporunu artefakt olarak yükleyin. Bu yaklaşımda hata mesajı doğrudan örneğin 'request body schema: customerId became required' şeklinde olur ve kod incelemesinde tartışılabilir bir kanıt sağlar.

  • Yeni response alanı ekliyorsanız istemcilerin unknown field toleransını doğrulayın. Java istemcilerinde Jackson için FAIL_ON_UNKNOWN_PROPERTIES açık ise additive değişiklik bile kırıcıdır.
  • Enum değerini kaldırmak yerine deprecated durumda bırakın. Mobil istemciler eski değeri cache'leyip günler sonra gönderebilir.
  • URL path'ini değiştirmek yerine eski path'i sunset tarihiyle koruyun; yalnızca OpenAPI'deki deprecated: true işareti yönlendirme davranışı sağlamaz.

spring boot eğitimi kapsamında bu kontrolü yerel olarak da çalıştırın: geliştirici pull request açmadan önce ./mvnw verify && ./scripts/openapi-diff.sh komutunu çalıştırabilmelidir. Diff'in hızlı ve tekrarlanabilir olması, sözleşme denetimini yalnızca release yöneticisinin yaptığı geç kontrol olmaktan çıkarır.

Java microservices için ProblemDetail hata sözleşmesini sürümleyin

HTTP hata metnini serbest biçimli bir message alanına koymak, tüketicileri doğal dil metni ayrıştırmaya iter. Bunun yerine RFC 9457 uyumlu ProblemDetail kullanın ve makine tarafından okunacak hata kodunu extension alanına ekleyin. type alanı sabit bir URI olmalı, title alanı değişebilir insan metni olmalıdır. İstemci kararını title veya detail ile değil type ve code ile vermelidir.

@RestControllerAdvice
class ApiExceptionHandler {

    @ExceptionHandler(OrderNotFoundException.class)
    ProblemDetail orderNotFound(OrderNotFoundException ex) {
        ProblemDetail problem = ProblemDetail.forStatus(HttpStatus.NOT_FOUND);
        problem.setType(URI.create("https://api.example.com/problems/order-not-found"));
        problem.setTitle("Order was not found");
        problem.setDetail("No order exists for the supplied identifier");
        problem.setProperty("code", "ORDER_NOT_FOUND");
        problem.setProperty("orderId", ex.orderId().toString());
        return problem;
    }
}

Validation hatalarında alan adı, reddedilen değer ve kuralı ayrı taşıyın. Reddedilen değeri her zaman response'a yazmayın: parola, token, kart numarası ve PII içeren alanlarda veri sızıntısı oluşur. Aşağıdaki gibi sadece field ve rule üretmek, React veya başka bir istemcinin alan bazlı hata gösterebilmesini sağlar. Bu ayrım java fullstack eğitimi projelerinde frontend'in backend metnine bağımlı hale gelmesini önler.

problem.setProperty("violations", List.of(
    Map.of("field", "deliveryDate", "rule", "must_be_future")
));

Önemli edge case şudur: HTTP 400 altında farklı domain hatalarını tek bir generic validation type'ına toplarsanız telemetry kaybolur. OpenTelemetry span'ına error.type olarak ProblemDetail type URI'sini ekleyin ve dashboard sorgusunu status code yerine bu alanla kurun. Örneğin sum(rate(http_server_request_duration_seconds_count{error_type="order-not-found"}[5m])) benzeri bir metrik, 404 oranındaki değişimin gerçekten eksik kaynak mı yoksa yanlış istemci çağrısı mı olduğunu ayrıştırır.

Spring Data JPA ve Hibernate ORM değişikliklerini API'den ayırın

spring data jpa repository'sinden dönen entity'yi doğrudan response olarak yayınlamak, Hibernate proxy davranışını ve kolon eklemelerini istemci sözleşmesine sızdırır. Özellikle LAZY ilişki JSON serileştirilirken transaction dışında erişilirse LazyInitializationException, transaction içinde erişilirse beklenmeyen ek sorgular üretir. Entity ile API DTO'sunu ayırın ve repository sorgusunu DTO için gereken alanlarla sınırlayın.

@Query("""
    select new com.example.api.OrderSummary(o.id, o.status, c.displayName)
    from Order o join o.customer c
    where o.createdAt > :from
    order by o.createdAt desc, o.id desc
    """)
Slice<OrderSummary> findRecent(@Param("from") Instant from, Pageable pageable);

Buradaki ikinci sıralama anahtarı olan o.id desc kritik bir ayrıntıdır. Yalnızca createdAt ile sıralanan offset pagination, aynı timestamp'e sahip kayıtlar eklendiğinde sayfalar arasında kayıt atlayabilir veya tekrarlayabilir. Yüksek yazma trafiğinde cursor'u createdAt,id ikilisi olarak taşıyan keyset pagination kullanın. hibernate orm tarafında sorgu sayısını görmek için test profilinde org.hibernate.SQL yerine Hibernate Statistics veya datasource-proxy kullanın; SQL log'u hacimli sistemlerde ölçüm aracı değildir.

Önce-sonra doğrulaması için aynı endpoint'e sabit bir test verisiyle 100 istek gönderin ve datasource-proxy üzerinden request başına sorgu sayısını kaydedin. DTO projection öncesinde örneğin request başına 21 SELECT, sonrasında 1 SELECT görmeyi hedefleyen bir regresyon eşiği koyabilirsiniz. Bu, java backend geliştirme ekiplerinde API response alanı eklenince sessizce oluşan N+1 regresyonunu CI testinde görünür yapar.

Spring AI, Spring MCP ve Model Context Protocol araç sözleşmeleri

spring ai ile bir iş fonksiyonunu tool olarak açtığınızda, doğal dil arayüzü yeni bir istemci türü ekler; bu istemci de API sözleşmesine ihtiyaç duyar. spring mcp üzerinden yayınlanan Model Context Protocol araçlarında parametre açıklaması yeterli değildir: kimlik doğrulama kapsamı, tenant sınırı ve sonuç şeması tool seviyesinde korunmalıdır. Araç, serbest metin yerine küçük ve kararlı bir DTO dönmelidir.

@Service
class OrderTools {
    private final OrderQueryService orders;

    OrderTools(OrderQueryService orders) {
        this.orders = orders;
    }

    @Tool(description = "Returns the status of one order owned by the authenticated customer")
    OrderStatusView getOrderStatus(
            @ToolParam(description = "Order UUID") UUID orderId,
            Authentication authentication) {
        return orders.findOwnedStatus(orderId, authentication.getName());
    }
}

record OrderStatusView(UUID id, String status, Instant updatedAt) {}

Yaygın hata, tool metoduna sadece orderId verip tenant veya kullanıcı kontrolünü modelin prompt'una bırakmaktır. Model yanlış veya saldırgan bir istekte başka müşterinin UUID'sini çağırabilir; prompt bir yetkilendirme mekanizması değildir. Service katmanında findOwnedStatus sorgusunu where order.id = :id and customer.subject = :subject koşuluyla uygulayın ve bulunamayan ile yetkisiz kaynak için dışarıya aynı 404 ProblemDetail type'ını döndürün.

MCP istemcisiyle entegrasyondan önce MCP Inspector kullanarak tool listesini, input schema'yı ve hata yanıtlarını doğrulayın. Tool açıklamasında 'current user' gibi belirsiz ifadeler yerine hangi güvenlik bağlamının kullanıldığını yazın. Bu test, java microservices içinde bir REST endpoint'i değiştirmeden yalnızca tool DTO'suna alan eklenmesiyle oluşabilecek AI istemci uyumsuzluklarını yakalar.

Sözleşme testini consumer tarafına taşıyın

OpenAPI diff provider'ın yüzeyini denetler, fakat tüketicinin gerçekten hangi alanı kullandığını göstermez. Spring Cloud Contract ile consumer'ın beklediği minimum response'u provider testine çevirin. Aşağıdaki sözleşme, order response'unda id ve status alanlarını zorunlu kılar; provider bu alanlardan birini kaldırırsa generated test başarısız olur.

Contract.make {
    description "consumer can read order status"
    request {
        method GET()
        urlPath('/orders/4e8c4a40-bd6f-4a1f-9e9d-9d1a8e11a111')
    }
    response {
        status OK()
        headers { contentType(applicationJson()) }
        body([
            id: "4e8c4a40-bd6f-4a1f-9e9d-9d1a8e11a111",
            status: "PAID"
        ])
    }
}

Consumer contract'i tüm response gövdesini eşleştirecek kadar katı yazmayın. Tüketicinin kullanmadığı alanları sabitlemek, zararsız alan eklemelerini gereksiz release koordinasyonuna dönüştürür. Buna karşılık status enum'u consumer için karar noktasıysa regex veya izinli değer listesiyle açıkça doğrulayın. Bu denge, java programlama eğitimi materyallerindeki basit mock testlerinden daha gerçekçidir: mock'un değil, dağıtılacak provider'ın sözleşmesi doğrulanır.

Sık Sorulan Sorular

Spring Boot eğitiminde OpenAPI diff CI pipeline'a nasıl eklenir?

Build sonunda springdoc-openapi ile target/openapi.json üretin, son production tag'inden indirilen baseline spec ile openapi-diff çalıştırın ve --fail-on-incompatible exit code'unu merge kontrolü yapın. Required request alanı ekleme, enum değeri silme ve response status kaldırma kurallarını kırıcı değişiklik olarak kabul edin.

Spring Data JPA ve Hibernate ORM entity'leri doğrudan REST response döndürülebilir mi?

Döndürülebilir ama önerilmez. LAZY ilişkiler serileştirme sırasında ek sorgu veya LazyInitializationException üretebilir, ayrıca entity'ye eklenen kolon istemci şemasına fark etmeden sızar. DTO projection kullanın, Hibernate Statistics veya datasource-proxy ile endpoint başına SELECT sayısını ölçün ve regresyon eşiğini testte doğrulayın.

Spring AI ve Spring MCP ile Model Context Protocol tool güvenliği nasıl kurulur?

Tool parametresinden gelen kaynak kimliğini service katmanında Authentication subject veya tenant ile birlikte sorgulayın. Yetki kararını prompt'a bırakmayın. MCP Inspector ile input schema ve tool hata çıktısını test edin; tool response'unu entity yerine dar bir DTO olarak yayınlayın.

Java fullstack eğitimi projelerinde ProblemDetail neden önemlidir?

Frontend'in doğal dil hata mesajı ayrıştırmasını önler. ProblemDetail içine sabit type URI, makine kodu ve PII içermeyen violations dizisi koyun. İstemci field ve rule alanıyla form hatasını gösterebilir; backend title veya detail metnini değiştirdiğinde kullanıcı arayüzü kırılmaz.

AI / LLM Discovery

Bu makale Opendart Akademi Java eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt