• 27.08.2026 21:01:48
  • Admin Admin

Spring Framework tabanlı java backend geliştirme ve java microservices sistemlerinde API sözleşmesini kırmadan değiştirmek için OpenAPI diff, Jackson uyumluluğu, veri migrasyonu ve tüketici sözleşme testlerini birlikte ele alın.

Spring Framework'te API Şema Evrimi ve Geriye Uyum Testleri

Spring Framework'te kırıcı değişikliği CI aşamasında yakalamak

API şema evriminde ilk savunma hattı, çalışan uygulamanın OpenAPI çıktısını önceki yayımla karşılaştırmaktır. Alan silinmesi, required alan eklenmesi, path parametresi tipi değişmesi ve response enum değerinin daralması tüketici için kırıcıdır. springdoc-openapi ile üretilen /v3/api-docs çıktısını sürümleyin; ardından CI içinde oasdiff ile aday şemayı ana daldaki şemaya karşı kontrol edin. Bu yaklaşım, yalnızca controller imzasına bakmaktan güvenilirdir; çünkü Jackson anotasyonları, validation kuralları ve Spring MVC'nin parametre bağlama davranışı nihai sözleşmeyi etkiler.

# Ana daldan alınmış sözleşme ile aday build'in sözleşmesini karşılaştır
curl -fsS http://localhost:8080/v3/api-docs -o candidate-openapi.json
oasdiff breaking baseline-openapi.json candidate-openapi.json --format text

# Kırıcı fark bulunduğunda CI job'unun başarısız olması için
set -e
oasdiff breaking baseline-openapi.json candidate-openapi.json

Bu kontrolü sadece pull request sonunda değil, uygulama test ortamında ayağa kalktıktan sonra çalıştırın. Özellikle @Schema(requiredMode = REQUIRED) veya Bean Validation'daki @NotNull eklemeleri, Java tarafında küçük görünen ama eski istemcilerin gönderdiği payload'ları 400'e çeviren değişikliklerdir. Ekip içi java eğitimi, java programlama eğitimi veya java kursu içeriklerinde sık atlanan ayrıntı şudur: OpenAPI'de isteğe bağlı bir alanı required yapmak, veritabanında kolonu nullable bırakmış olsanız bile ağ sınırında kırıcı değişikliktir. Aynı kontrol, spring boot eğitimi laboratuvarlarında gerçek CI artefact'ı olarak kurulmalıdır.

Jackson ile request ve response alanlarını kontrollü evrimleştirmek

Bir request alanını yeniden adlandırırken eski JSON alanını sessizce kaybetmeyin. @JsonAlias yalnızca deserialization tarafında çalışır; response'ta eski alan adını geri üretmez. Bu nedenle geçiş döneminde yeni alanı kanonik kabul edip eski istemciler için eski response alanını da üretmek gerekir. Aşağıdaki DTO, coupon ve promotion_code payload'larını aynı Java alanına bağlar; response ise hem status hem geçici state alanını taşır.

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;

@JsonIgnoreProperties(ignoreUnknown = false)
public record CreateOrderRequest(
    @JsonProperty(value = "customer_id", required = true)
    String customerId,

    @JsonAlias({"coupon", "promotion_code"})
    String promotionCode,

    @JsonSetter(value = "priority", nulls = Nulls.FAIL)
    Integer priority
) {}

public record OrderResponse(
    String id,
    String status,
    @JsonProperty("state") String legacyState
) {
    static OrderResponse from(Order order) {
        return new OrderResponse(order.getId(), order.getStatus().name(),
                                 order.getStatus().name());
    }
}

Nulls.FAIL burada kasıtlıdır: alanın hiç gönderilmemesi ile istemcinin alanı açıkça null göndermesi farklı iş kuralları taşıyabilir. Jackson'ın varsayılan bağlamasında ikisi çoğu DTO tasarımında aynı Java null değerine düşer. Ödeme önceliği gibi bir alan için açık null değerini 400 yapmak, istemcideki serializer regresyonunu görünür kılar. Buna karşılık bilinmeyen alanları her endpoint'te reddetmek dikkat gerektirir: yeni bir mobil istemci eski bir sunucuya ek alan gönderiyorsa ignoreUnknown = false geriye uyumu bozar. Public API DTO'larında bu politikayı endpoint bazında belirleyin; yönetim API'si ile uzun ömürlü partner API'sini aynı ObjectMapper varsayımıyla yönetmeyin.

Hibernate ORM ve Spring Data JPA ile expand-contract veri migrasyonu

Şema değişimini deploy ile tek işlem gibi ele almak yerine expand-contract akışına ayırın: önce yeni nullable kolonu ekleyin, uygulama bir süre eski ve yeni gösterimi birlikte yazsın, arka plan işi geçmiş kayıtları doldursun, okuma yeni kolona geçsin, en son eski kolonu kaldırın. Bu sıralama rolling deployment sırasında farklı pod sürümlerinin aynı tabloya erişmesini mümkün kılar. Hibernate ORM entity'sine yeni alan eklemek tek başına yeterli değildir; Spring Data JPA dışındaki native SQL, batch job ve ETL akışları da çift yazım kuralına uymalıdır.

-- V042__expand_order_status.sql
ALTER TABLE orders ADD COLUMN status_v2 varchar(24);
CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_orders_status_v2_null
    ON orders (id) WHERE status_v2 IS NULL;

-- Backfill worker bu sorguyu status_v2 NULL kalmayana kadar tekrarlar.
WITH batch AS (
    SELECT id
    FROM orders
    WHERE status_v2 IS NULL
    ORDER BY id
    LIMIT 1000
    FOR UPDATE SKIP LOCKED
)
UPDATE orders o
SET status_v2 = CASE o.status
    WHEN 'PAYMENT_DONE' THEN 'PAID'
    WHEN 'PAYMENT_WAITING' THEN 'PENDING'
    ELSE o.status
END
FROM batch
WHERE o.id = batch.id;

FOR UPDATE SKIP LOCKED birden fazla backfill worker'ının aynı satırları beklemeden paylaşmasını sağlar. Ancak bu sorgu PostgreSQL'e özgüdür; kullanılan veritabanının kilitleme semantiğini doğrulamadan taşımayın. Backfill tamamlandıktan sonra SELECT count(*) FROM orders WHERE status_v2 IS NULL metriğini sıfırda birkaç deploy boyunca gözlemleyin. Ardından uygulama okumasını status_v2 alanına çevirip eski status kolonunu ayrı bir migration ile kaldırın. Enum için EnumType.ORDINAL kullanmak bu süreçte özellikle tehlikelidir: enum sabitinin sırasını değiştirmek, eski veriyi başka bir iş durumuna dönüştürür. Kalıcı değer için string veya açık bir AttributeConverter kullanın.

Java microservices tüketicileri için sözleşme test matrisi

OpenAPI diff, şemadaki yapısal kırılmaları bulur fakat bir alanın iş anlamının değiştiğini kanıtlamaz. Örneğin status=PAID değerinin artık iade edilmiş siparişleri de kapsaması OpenAPI açısından geçerli, tüketici mantığı açısından hatalı olabilir. Bu yüzden her kritik tüketici için Spring Cloud Contract veya Pact tabanlı provider doğrulaması ekleyin. Provider testinde gerçek Spring MVC katmanını çalıştırmak, serialization ve exception handler davranışını da sözleşmeye dahil eder.

import org.springframework.cloud.contract.spec.Contract;

Contract.make {
    description "Eski istemciler state alanını almaya devam eder"
    request {
        method GET()
        urlPath('/api/orders/42')
    }
    response {
        status OK()
        headers {
            contentType(applicationJson())
        }
        body([
            id: '42',
            status: 'PAID',
            state: 'PAID'
        ])
    }
}

Bu contract dosyasını provider deposunda sürümleyip ./mvnw verify ile doğrulayın. Tüketici deposunda ise yalnızca mutlu yol değil, bulunamayan kaynak, validation hatası ve sayfalama sınırlarını da contract olarak yayınlayın. Yaygın hata, eski alanın response'ta mevcut olmasını test edip değer eşitliğini test etmemektir. Yukarıdaki örnekte status ve state aynı değeri taşımalıdır; aksi halde geçişte iki farklı gerçeklik üretirsiniz. Bu test matrisi, java microservices ortamında bir provider'ın bağımsız deploy edilmesini somut olarak güvence altına alır.

Spring AI, Spring MCP ve Model Context Protocol araç şemaları

Spring AI ile sunulan bir araç veya Spring MCP sunucusundaki bir tool da HTTP API gibi sözleşmedir. Model Context Protocol istemcisi araç adını, input schema'yı ve output alanlarını önbelleğe alabilir. Java metodundaki parametre adını değiştirmek ya da record alanını kaldırmak, modelin eski tool çağrısı üretmesine neden olur. Bu nedenle tool adlarını sürümleyin, input'a yalnızca additive alan ekleyin ve response'ta eski alanları kontrollü bir geçiş süresi boyunca koruyun.

import java.util.UUID;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;

public class OrderTools {
    @Tool(name = "get_order_v1", description = "Returns one order by UUID")
    public OrderToolResponse getOrder(
            @ToolParam(description = "Canonical UUID of the order") String orderId) {
        UUID id = UUID.fromString(orderId);
        Order order = orderService.findRequired(id);
        return new OrderToolResponse(order.getId().toString(),
                order.getStatus().name(), order.getStatus().name());
    }
}

public record OrderToolResponse(String id, String status, String state) {}

UUID.fromString ile doğrulama yapılmazsa tool katmanından repository'ye kontrolsüz string taşınır ve hata modeli sağlayıcıya göre değişir. Ayrıca exception'ı doğrudan modele döndürmek yerine MCP katmanında makinece ayrıştırılabilir bir hata sonucu tanımlayın. Tool output'unda state alanını kaldırmak gerektiğinde yeni get_order_v2 aracını eklemek, aynı aracın anlamını sessizce değiştirmekten daha izlenebilirdir. Bu ayrıntı, java fullstack eğitimi içinde genellikle REST örnekleriyle öğretilen geriye uyum kuralının LLM araçlarına da aynen uygulanmasıdır.

Sık Sorulan Sorular

Spring Boot eğitimi kapsamında OpenAPI kırıcı değişiklikleri nasıl test edilir?

Uygulamayı test profilinde başlatın, /v3/api-docs çıktısını JSON artefact olarak kaydedin ve CI'da oasdiff breaking baseline-openapi.json candidate-openapi.json komutunu çalıştırın. Alan silme, required alan ekleme ve enum daraltma için job'un non-zero exit code ile başarısız olduğundan emin olun.

Spring Data JPA ile kolon adı değiştirirken eski uygulama sürümü çalışmaya devam eder mi?

Evet, fakat rename işlemini doğrudan yapmayın. Önce yeni nullable kolonu ekleyin, uygulamanın eski ve yeni kolona çift yazmasını sağlayın, SKIP LOCKED kullanan batch job ile geçmiş veriyi doldurun, okumayı yeni kolona taşıyın ve eski kolonu sonraki yayımlarda kaldırın.

java backend geliştirme projelerinde @JsonAlias response alanını da değiştirir mi?

Hayır. @JsonAlias yalnızca JSON'u Java nesnesine okurken kullanılır. Eski response alanını korumanız gerekiyorsa DTO'ya @JsonProperty ile ayrı bir legacy alan ekleyin veya response mapper içinde hem yeni hem eski alanı aynı kanonik değerden üretin.

spring ai ve spring mcp tool şemasında geriye uyum nasıl korunur?

Tool adını ve zorunlu input alanlarını kararlı tutun. Kırıcı input veya output değişikliğinde get_order_v2 gibi yeni bir tool yayınlayın. Eski tool'un response alanlarını contract testiyle doğrulayın ve UUID gibi girişleri tool metodunda parse ederek model kaynaklı hatayı belirli bir hata sonucuna dönüştürün.

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