Spring Boot

Spring Boot
Bu sayfanın QR kodu

Spring Boot 4 ve Spring Framework 7 çizgisinde otomatik yapılandırma, bağımlılık enjeksiyonu, MVC/REST, Jakarta Persistence, transaction, güvenlik, test, HTTP istemcileri, virtual thread, gözlemlenebilirlik, Native Image ve üretim güvenilirliğini ele alan ileri ders notu.

Spring Boot, Spring Framework üzerinde çalışan uygulamalarda altyapı yapılandırmasını, bağımlılık sürümlerini, çalışma zamanı entegrasyonlarını ve paketlemeyi tutarlı varsayımlarla bir araya getirir. Çerçevenin asıl değeri yalnız birkaç anotasyonla HTTP uç noktası açmak değildir; bağımlılık enjeksiyonu, dış yapılandırma, veri erişimi, işlem (transaction) sınırları, güvenlik, test, gözlemlenebilirlik ve dağıtım kararlarını aynı uygulama yaşam döngüsünde yönetebilmesidir.

Spring Boot 4.1.1, Spring Framework 7.0.9 veya üzerini gerektirir; en az Java 17 ile çalışır ve resmî uyumluluk aralığı Java 26'ya kadar uzanır. Örneklerde Java 25 LTS kullanılabilir. Java 27 yayımlanmış olsa da Spring Boot 4.1.1'in resmî sistem gereksinimleri henüz Java 27 desteğini ilan etmez. Sürüm yükseltmelerinde uygulama koduyla birlikte starter adları, Jakarta paketleri, JSON yığını, test modülleri ve üçüncü taraf entegrasyonları kontrol edilmelidir.

Ünite 1: Spring Boot Temelleri, Proje ve Yapılandırma

1.1. Spring Framework ile Spring Boot Arasındaki Sınır

Spring Framework; IoC kapsayıcısı, bağımlılık enjeksiyonu, AOP, işlem yönetimi, web katmanı, veri erişimi ve entegrasyon bileşenlerini sağlar. Spring Boot bu yeteneklerin yerine geçen ayrı bir framework değildir. Spring ekosistemini belirli sürüm kümeleri, otomatik yapılandırmalar, starter bağımlılıkları, executable paketleme ve işletim özellikleriyle daha deterministik biçimde başlatır.

Temel ayrım şöyledir:

Spring Framework = programlama modeli + altyapı soyutlamaları
Spring Boot      = opinionated başlangıç + auto-configuration + sürüm yönetimi + işletim katmanı

Boot'un varsayımları zorunlu değildir. Uygulama kendi bean'ini veya açık yapılandırmasını sağladığında birçok otomatik yapılandırma geri çekilir. Bu davranış @ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty gibi koşullar üzerinden kurulur.

1.2. Boot 4 Proje Tabanı ve Yapı Sistemi

Spring Boot 4 ile starter yapısı daha modüler hâle gelmiştir. MVC tabanlı yeni projelerde spring-boot-starter-webmvc tercih edilir; eski spring-boot-starter-web geçiş amacıyla bulunmakla birlikte deprecated durumdadır.

Maven için minimal bir servis tabanı:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>4.1.1</version>
</parent>

<properties>
  <java.version>25</java.version>
</properties>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webmvc</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
  </dependency>
</dependencies>

Boot dependency management kullanıldığında, yönetilen bağımlılıkların sürümlerini rastgele tek tek sabitlemek yerine uyumlu BOM çizgisi korunmalıdır. Güvenlik veya hata düzeltmesi nedeniyle override gerekiyorsa bunun neden ve etkisi ayrıca kaydedilmelidir.

1.3. Uygulama Başlangıcı ve Paket Sınırı

Tipik giriş noktası @SpringBootApplication ile tanımlanır:

package tr.example.orders;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

Bu anotasyon; yapılandırma, component scanning ve auto-configuration davranışlarını bir araya getirir. Ana sınıfı uygulamanın kök paketinde tutmak, bileşen ve entity taramasının doğal sınırını oluşturur. Geniş scanBasePackages kullanmak başlangıçta kolay görünse de modül sınırlarını belirsizleştirebilir.

1.4. IoC Kapsayıcısı, Bean ve Bağımlılık Enjeksiyonu

Spring nesne grafiğini ApplicationContext içinde yönetir. @Component, @Service, @Repository, @Controller ve @RestController farklı katman niyetlerini ifade eder; her birini yalnız “bean üretme etiketi” olarak görmek mimari bilgiyi kaybettirir.

Constructor injection varsayılan seçim olmalıdır:

@Service
public final class PriceService {
    private final TaxPolicy taxPolicy;

    public PriceService(final TaxPolicy taxPolicy) {
        this.taxPolicy = taxPolicy;
    }

    public BigDecimal gross(final BigDecimal net) {
        return net.add(taxPolicy.taxFor(net));
    }
}

Constructor injection bağımlılıkları görünür, zorunlu ve test edilebilir hâle getirir. Alan enjeksiyonu ise nesnenin geçerli durumunu constructor dışına taşır ve framework olmadan örneklemeyi zorlaştırır.

Birden fazla implementasyon varsa @Qualifier, @Primary veya daha açık bir domain factory kullanılabilir. Çözüm, belirsizliği rastgele bean adına bırakmamalıdır.

1.5. Otomatik Yapılandırma Nasıl Çalışır

Spring Boot otomatik yapılandırmada koşul değerlendirmesi ve kullanıcı bean tanımında geri çekilme

Auto-configuration classpath, mevcut bean'ler, property'ler ve uygulama türü gibi sinyallere göre koşullu bean tanımları sağlar. Boot 4'te auto-configuration altyapısı daha küçük teknoloji modüllerine ayrılmıştır; eski tek büyük spring-boot-autoconfigure algısıyla paket yapısına bağımlı kod yazılmamalıdır.

Auto-configuration bir “sihir” değildir. Hangi yapılandırmanın devreye girdiğini anlamak için:

  • koşullu anotasyonlara,
  • configuration özellikler üst verisine,
  • condition evaluation report'a,
  • uygulamadaki explicit bean tanımlarına

bakılır.

Starter eklemek uygulamaya yalnız jar eklemek değil, çoğu durumda belirli auto-configuration sınıflarını da erişilebilir hâle getirmek demektir.

1.6. Dış Yapılandırma ve Tip Güvenli Özellikler

Bağlantı adresleri, zaman aşımı değerleri, özellik bayrakları ve ortam bağımlı seçenekler kod sabiti olmamalıdır. Spring Boot property kaynaklarını belirli bir precedence ile birleştirir. Aynı anahtar farklı kaynaklarda tanımlanmışsa daha yüksek öncelikli kaynak sonucu belirler.

İlişkili ayarları @ConfigurationProperties ile tip güvenli modele bağlamak, çok sayıda dağınık @Value kullanımından daha sürdürülebilirdir:

package tr.example.orders.config;

import java.time.Duration;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("orders.remote")
public record RemoteOrderProperties(String baseUrl, Duration connectTimeout, Duration readTimeout) {
}
@Configuration
@EnableConfigurationProperties(RemoteOrderProperties.class)
class OrderConfiguration {
}

YAML karşılığı:

orders:
  remote:
    base-url: https://api.example.test
    connect-timeout: 2s
    read-timeout: 5s

Parola, token ve özel anahtarlar normal source-control yapılandırmasına yazılmamalıdır. Secret yönetimi, dağıtım ortamının güvenlik modeline göre ayrı ele alınmalıdır.

1.7. Profil Kullanımı ve Ortam Ayrımı

dev, test, prod gibi profiller farklı bean veya property kümelerini etkinleştirebilir. Profil, iş kuralı seçme mekanizmasına dönüşmemelidir. İş davranışını environment adına göre dallandırmak yerine domain politikası veya açık configuration tercih edilmelidir.

Aynı binary'nin farklı ortamda yalnız yapılandırmayla çalışabilmesi; release bütünlüğü, rollback ve hata analizi açısından değerlidir.

Native Image kullanıldığında build-time AOT analizi nedeniyle çalışma anında bean grafiğini değiştiren bazı dinamik profil/koşul kalıplarının sınırlamaları ayrıca değerlendirilmelidir.

Ünite 2: Web Katmanı, HTTP Sözleşmeleri ve Doğrulama

Bir web isteğinin işlenmesi tasarlanırken önce taşıma biçimi ile iş kuralı ayrılır. Girdi sözleşmesi ve doğrulama koşulları tanımlandıktan sonra yetki denetimi, iş işlemi ve sonuç eşleme aşamaları belirlenir. İstemcinin geçersiz verisi ile sunucudaki beklenmeyen hata aynı yanıt türüne indirgenmemelidir. Uç noktanın doğru çalışması, yalnız başarılı JSON üretmesiyle değil, hatalı isteklerde tutarlı durum kodları ve yan etki üretmeme koşuluyla doğrulanır.

2.1. Spring MVC İstek İşleme Zinciri

Spring MVC isteğinin filtre zinciri, DispatcherServlet, interceptor preHandle sırası ve controller üzerinden geçişi ile afterCompletion çağrılarının ters sırada çıkışı
Spring MVC istek zinciri

Servlet tabanlı Spring MVC akışının merkezinde DispatcherServlet vardır. İstek uygun handler mapping üzerinden controller metoduna yöneltilir; argument resolver'lar parametreleri hazırlar; dönüş değeri message converter veya view resolver tarafından işlenir.

HTTP isteği
    |
    v
Filter zinciri
    |
    v
DispatcherServlet
    |
    v
HandlerMapping -> Controller -> Service -> Repository
                                     |
                                     v
                                  database
    |
    v
HTTP yanıtı
@RestController
@RequestMapping("/api/orders")
public final class OrderController {
    private final OrderService service;

    public OrderController(final OrderService service) {
        this.service = service;
    }

    @GetMapping("/{id}")
    public OrderResponse find(@PathVariable final long id) {
        return service.find(id);
    }
}

Controller'ın repository'ye doğrudan erişmesi kısa örnekte az kod üretir; büyük sistemde authorization, transaction ve domain kuralı sınırlarını karıştırır. Controller HTTP sözleşmesini, service use-case'i, repository ise persistence sınırını temsil etmelidir.

2.2. HTTP Sözleşmesi ve REST API Tasarımı

REST uç nokta tasarımında URI, HTTP metodu, durum kodu ve temsil formatı birlikte sözleşmedir. GET veri okumak, POST çoğunlukla yeni işlem/kaynak başlatmak, PUT belirli kaynağın tam idempotent güncellemesini, PATCH kısmi değişikliği, DELETE silme semantiğini ifade eder.

Her CRUD metodunu otomatik olarak dış API yapmak doğru değildir. Domain komutu çoğu zaman daha açık bir URI ve body gerektirir.

public record CreateOrderRequest(@NotBlank String customerCode,
                                 @NotEmpty List<@Valid OrderLineRequest> lines) {
}

public record OrderLineRequest(@NotBlank String productCode,
                               @Positive int quantity) {
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public OrderResponse create(@Valid @RequestBody final CreateOrderRequest request) {
    return service.create(request);
}

DTO ile entity ayrımı, dış sözleşmenin persistence modeline bağımlı olmasını önler. Entity'yi doğrudan JSON'a açmak lazy-loading, recursive graph, veri sızıntısı ve versiyonlama sorunları doğurabilir.

2.3. JSON: Boot 4 ile Jackson 3

Spring Boot 4'te JSON için tercih edilen ve varsayılan Jackson sürümü Jackson 3'tür. Jackson 2 desteği geçiş amacıyla korunmuş fakat deprecated edilmiştir. Boot 3'ten yükseltirken package değişimleri, modül uyumluluğu ve özel serializer/deserializer sınıfları kontrol edilmelidir.

JSON modelinde eksik alan, null, bilinmeyen alan, tarih/saat biçimi ve enum değişiklikleri API uyumluluğunun parçasıdır. İstemci ile sunucunun aynı Java sınıfını paylaşması sözleşme yönetiminin yerine geçmez.

XML gerekiyorsa content negotiation ve uygun message converter kullanılabilir; fakat JSON ve XML aynı nesne grafiğini farklı güvenlik davranışlarıyla dışarı sızdırmamalıdır.

2.4. Doğrulama: Sözdizimsel Doğruluk ile İş Kuralını Ayırmak

Jakarta Bean Validation; boşluk, uzunluk, sayı aralığı ve format gibi sınır doğrulamalarında etkilidir. “Müşterinin kredi limiti yeterli mi?” gibi veriye ve bağlama bağlı kurallar ise service/domain katmanında ele alınmalıdır.

public record RegisterCustomerRequest(
        @NotBlank @Size(max = 80) String name,
        @Email @Size(max = 254) String email) {
}

@Valid yalnız doğru sınırda uygulanmalıdır. Bir request içinde yüz binlerce nested nesneyi sınırsız validate etmek CPU ve bellek yönünden saldırı yüzeyi oluşturabilir; payload boyutu ve koleksiyon limitleri ayrıca sınırlandırılmalıdır.

2.5. Hata Yönetimi ve Problem Details

HTTP hata cevabı stack trace dökmek değildir. API; hata kategorisi, stabil hata kodu, güvenli mesaj ve gerektiğinde correlation kimliği gibi kontrollü alanlar üretmelidir.

Spring Framework'ün ProblemDetail desteği RFC 9457 uyumlu hata temsili kurmayı kolaylaştırır:

@RestControllerAdvice
public final class ApiExceptionHandler {
    @ExceptionHandler(OrderNotFoundException.class)
    public ResponseEntity<ProblemDetail> notFound(final OrderNotFoundException ex) {
        ProblemDetail detail = ProblemDetail.forStatus(HttpStatus.NOT_FOUND);
        detail.setTitle("Order not found");
        detail.setProperty("code", "ORDER_NOT_FOUND");
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(detail);
    }
}

Veritabanı ayrıntısı, SQL, dosya yolu, secret, token veya iç sınıf adı dış istemciye taşınmamalıdır. Operasyon ekibinin ihtiyaç duyduğu teknik detay ayrı log/izleme kanalında tutulur.

Ünite 3: Kalıcılık, Transaction ve Şema Yönetimi

Kalıcılık katmanında bir iş akışı önce hangi işlemlerin aynı transaction içinde tamamlanacağı üzerinden modellenir. Okuma ve yazma sınırları, yalıtım düzeyi ve veritabanı kısıtlarıyla birlikte değerlendirilir. ORM nesnesinin bellekte değişmiş olması değişikliğin kalıcı olarak yazıldığı anlamına gelmez; flush ve commit zamanları ayrıştırılmalıdır. Eşzamanlı güncellemelerde kayıp değişiklik olasılığı varsa sürüm denetimi veya uygun kilitleme kararı somut çatışma senaryolarıyla sınanır.

3.1. Spring Data JPA ve Jakarta Persistence

Spring Data JPA 4.x, Jakarta Persistence API üzerinde repository desteği sağlar. javax.persistence kullanan eski kod, modern Spring Boot çizgisinde jakarta.persistence paketlerine taşınmalıdır.

@Entity
@Table(name = "orders")
public class OrderEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 40)
    private String customerCode;

    @Version
    private long version;

    protected OrderEntity() {
    }

    public OrderEntity(final String customerCode) {
        this.customerCode = customerCode;
    }
}

Repository abstraction boilerplate'i azaltır; fakat sorgu maliyetini ortadan kaldırmaz. N+1, yanlış fetch stratejisi, büyük entity graph, kontrolsüz pagination ve gereksiz dirty checking üretimde temel performans sorunlarıdır.

public interface OrderRepository extends JpaRepository<OrderEntity, Long>, JpaSpecificationExecutor<OrderEntity> {
}

Derived query isimleri küçük sorgularda okunabilirdir. Karmaşık koşullarda Specification, explicit JPQL veya ihtiyaca uygun başka bir query modeli daha doğru olabilir.

3.2. Transaction Sınırı

@Transactional veritabanı transaction'ını method etrafında proxy/AOP ile yönetir. Transaction'ı controller boyunca açık tutmak yerine use-case sınırında tutmak daha öngörülebilirdir.

Controller
    |
    v
@Transactional Service
    |
    +--> JPA / JDBC --> database
    |                    |
    |                    +--> COMMIT
    |                    +--> ROLLBACK
    |
    +--> uzak servis: transaction sınırını dikkatle tasarla
@Service
public class OrderCommandService {
    private final OrderRepository repository;

    public OrderCommandService(final OrderRepository repository) {
        this.repository = repository;
    }

    @Transactional
    public long create(final String customerCode) {
        return repository.save(new OrderEntity(customerCode)).getId();
    }
}

Dikkat edilmesi gerekenler:

  • aynı sınıf içindeki self-invocation proxy'yi aşabilir,
  • checked/unchecked istisna rollback davranışı açıkça bilinmelidir,
  • uzak HTTP çağrısını uzun DB transaction'ı içinde bekletmek kilit süresini uzatır,
  • isolation seviyesi gerçek tutarlılık gereksinimine göre seçilmelidir,
  • retry ile transaction tekrarının idempotency etkisi birlikte düşünülmelidir.

Transaction süresi ile iş süresini ayırmak. Bir web isteğinin bütün ömrünü transaction içine almak çoğu zaman yanlış sahiplik sınırı oluşturur. Uzak HTTP çağrısı, mesaj kuyruğu onayı veya istemcinin yanıtını bekleme sırasında JDBC bağlantısının ve satır kilidinin açık kalması, iş yoğunlaştıkça başka isteklerin bekleme süresini büyütür. Kısa transaction, yalnız atomik olması gereken okuma/değiştirme/yazma adımlarını kapsamalıdır; harici yan etkiler için retry, idempotency veya outbox gibi ayrı protokol gerekir.

Spring'in proxy tabanlı transaction yönetiminde aynı nesne içindeki doğrudan metot çağrısı ilgili @Transactional advice'ını çalıştırmayabilir. Yeni bir virtual thread oluşturmak veya @Async sınırına geçmek de mevcut thread'e bağlı transaction bağlamını otomatik taşımaz. İşlemi hangi metot etiketinin taşıdığından önce, hangi fiziksel bağlantının ne zaman edinildiği, hangi anda commit/rollback yapıldığı ve başarısızlıkta dış dünyada hangi etkinin kaldığı gösterilmelidir.

3.3. Şema Yönetimi

ddl-auto=update geliştirme kolaylığıdır; kontrollü production migration stratejisi değildir. Şema değişiklikleri sürümlenmiş migration ile yönetilmelidir.

Boot 4, Flyway ve Liquibase entegrasyonlarını starter/modül yapısı üzerinden destekler. Migration dosyası dağıtım artefaktının bir parçası olarak source control'da izlenmeli; ileri ve geri uyumluluk uygulama rollout modeliyle birlikte planlanmalıdır.

Zero-downtime değişiklikte tipik sıra şöyledir:

genişlet -> yeni kodu dağıt -> veriyi dönüştür -> eski kullanımı kaldır -> daralt

Örneğin kolon adını tek dağıtımda değiştirmek yerine geçiş süresinde iki sürümün birlikte çalışabilmesi sağlanır.

Ünite 4: Güvenlik, Entegrasyon ve Yürütme Modelleri

4.1. Spring Security: Kimlik Doğrulama ve Yetkilendirme

PKCE ile OAuth 2 yetkilendirme kodu akışında code verifier ve code challenge sırası
OAuth 2 ve PKCE

Spring Security filter chain üzerinden authentication, authorization, CSRF, session, header ve diğer güvenlik kontrollerini uygular. Modern yapılandırmada SecurityFilterChain bean'i kullanılır:

@Configuration
@EnableMethodSecurity
public class SecurityConfiguration {
    @Bean
    SecurityFilterChain securityFilterChain(final HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/health").permitAll()
                        .anyRequest().authenticated())
                .httpBasic(Customizer.withDefaults())
                .build();
    }
}

Method security spring-boot-starter-security eklenince otomatik etkinleşmez; @EnableMethodSecurity açıkça etkinleştirilir. @PreAuthorize güncel ve ifade gücü yüksek seçenektir:

@PreAuthorize("hasAuthority('order:read')")
public OrderResponse find(final long id) {
    return queryService.find(id);
}

Kimlik doğrulama yapıldıktan sonra “kullanıcı giriş yaptı” demek yetmez. Nesne düzeyi authorization, tenant sınırı, kaynak sahipliği ve admin işlevleri ayrıca doğrulanmalıdır.

4.2. CSRF, CORS ve Oturum Modeli

CORS bir authentication veya authorization mekanizması değildir; tarayıcının cross-origin response paylaşım politikasını yönetir. CSRF ise özellikle cookie tabanlı kimlik doğrulamada kullanıcının tarayıcısını istemeden state-changing istek göndermeye zorlayan saldırı sınıfıdır.

Stateless bearer-token API ile server-side session kullanan web uygulamasının güvenlik ayarı aynı olmamalıdır. CSRF'yi yalnız “REST API” olduğu gerekçesiyle kapatmak yerine credential taşıma modeli incelenmelidir.

4.3. Uzak HTTP Çağrıları: RestClient, WebClient ve HTTP Service Clients

Spring Framework 7'de RestTemplate deprecated durumdadır. Yeni senkron kodda RestClient, non-bkiliting/reactive akışta WebClient, interface tabanlı istemci sözleşmelerinde HTTP Service Clients tercih edilir.

@Bean
RestClient inventoryClient(final RestClient.Builder builder, final RemoteOrderProperties properties) {
    return builder.baseUrl(properties.baseUrl()).build();
}
public interface InventoryApi {
    @GetExchange("/api/stock/{productCode}")
    StockResponse get(@PathVariable String productCode);
}

HTTP istemcisinde timeout, connection reuse, DNS, retry, circuit breaking, response boyutu ve idempotency ayrı kararlardır. Varsayılan timeout'a güvenmek kritik serviste kabul edilebilir bir politika değildir.

4.4. Senkron, Reaktif ve Virtual İş Parçacığı Modelleri

Spring MVC bloklayan programlama modeliyle çalışabilir; WebFlux ise Reactor tabanlı non-bkiliting akış içindir. “Reaktif daha hızlıdır” genellemesi doğru değildir. Model, I/O davranışı ve ekip karmaşıklığı birlikte değerlendirilmelidir.

bloklayan model:     istek -> thread --------> blocking I/O -> yanıt
virtual thread:      istek -> virtual thread -> blocking I/O -> yanıt
reaktif model:       istek -> event loop ----> nonblocking I/O -> sinyal

model seçimi -> iş yükü + kütüphane davranışı + backpressure gereksinimi

Spring Boot Java 21+ üzerinde virtual iş parçacığı kullanımını destekler:

spring.threads.virtual.enabled=true
spring.main.keep-alive=true

Boot belgeleri en iyi deneyim için Java 24+ önerir. Virtual iş parçacığı açıldığında klasik iş parçacığı-pool ayarlarının önemli bir kısmı artık aynı anlamı taşımaz. Pinned virtual iş parçacığı, bağlantı havuzu ve uzak sistem kapasitesi throughput sınırı olmaya devam eder.

Virtual iş parçacığı sınırsız dış servis veya veritabanı concurrency'si demek değildir. Database connection pool 50 ise aynı anda binlerce virtual iş parçacığı DB'ye girmek istediğinde dar boğaz yine pool'dur.

4.4.1. Virtual Thread Açıkken de Admission Control Gerekir

Spring Boot'ta virtual thread etkinleştirmek, bloklayan controller ve service kodunun daha fazla eşzamanlı isteği düşük thread maliyetiyle taşımasını sağlayabilir. Ancak uygulamanın aşağısındaki JDBC havuzu, uzak HTTP servisi, dosya sistemi veya CPU yoğun bölüm aynı anda işleyebileceği iş sayısını kendiliğinden artırmaz.

Bu nedenle uygulama sınırında iki ayrı soru sorulmalıdır:

İşi hangi yürütme modeli taşıyor?
              !=
Kıt kaynağa aynı anda kaç iş girebilir?

İkinci sorunun cevabı bağlantı havuzu, semaphore, kuyruk veya başka bir admission-control mekanizması olabilir. Örneğin yüzlerce virtual thread aynı pahalı uzak çağrıya yöneliyorsa, çağrı önünde sonlu bir izin bütçesi kullanmak hem yerel belleği hem uzak sistemi korur. İzin bekleme süresi de sınırsız olmamalıdır; zaman aşımı ve ret davranışı HTTP sözleşmesinin parçası olarak tanımlanmalıdır.

Bu koruma servlet thread pool ayarlarının yeni bir adı değildir. Amaç yürütme thread'lerini tekrar yapay biçimde kıtlaştırmak değil, gerçekten kıt olan bağımlılığın kapasitesini modellemektir. Kaynak başına farklı sınırlar gerekebilir; veritabanı ve uzak servis aynı concurrency bütçesini paylaşmak zorunda değildir.

Üretim gözlemlenebilirliğinde active permit, permit wait, timeout, reject ve aşağı akış latency birlikte izlenirse darboğazın thread modelinden mi yoksa bağımlılık kapasitesinden mi geldiği ayrıştırılabilir. Bu ayrım Java Tabanlı Veri Sistemlerinde Yüksek Başarım notundaki bağlantı havuzu ve kuyruk yaklaşımının Spring katmanındaki karşılığıdır.

Yürütme modelinin seçimi bir kapasite kanıtı gerektirir. Virtual thread, çok sayıda bekleyen görevin platform thread tüketimini azaltabilir; fakat veritabanı bağlantısı, dış servisin rate limit'i veya CPU çekirdeği sayısı değişmez. WebFlux da kendi başına daha yüksek iş hacmi sağlamaz; bloklayan JDBC işlemi olay döngüsünde çalıştırılırsa gecikme yayılabilir. Modelleri karşılaştırırken aynı handler davranışı, aynı veritabanı planı, aynı seri hâle getirme yükü ve aynı hata koşulları kullanılmalıdır.

Ölçüm tablosu yalnız RPS içermemelidir: aktif istek, JDBC bağlantı bekleme zamanı, CPU kullanımı, allocation rate, p99, timeout ve thread/virtual-thread sayıları aynı yük kademesi için raporlanmalıdır. Bir sürüm daha fazla bekleyen isteği hafızada tutabiliyor fakat tamamlanan iş sayısı değişmiyorsa, elde edilen sonuç kapasite artışı değil kuyruk konumunun değişmesidir. Yük kabul sınırı ve açık bir timeout bütçesi yürütme modelinden bağımsız gereksinimdir.

4.5. Önbellek ve Tutarlılık

Spring Cache abstraction @Cacheable, @CachePut ve @CacheEvict gibi anotasyonlarla önbellek sağlayıcısından bağımsız bir programlama modeli verir. Ancak önbellek invalidation domain kararıdır.

Cache anahtarı tenant, locale, authorization veya versiyon gibi sonucu etkileyen boyutları içermiyorsa veri sızıntısı veya yanlış cevap oluşabilir. TTL yalnız performans değil, kabul edilen bayatlık süresidir.

Dağıtık sistemde local önbellek her instance'ta farklı durum üretebilir. Tutarlılık ihtiyacı yüksek veride önbellek eklemek yerine önce query ve indeks maliyeti ölçülmelidir.

Önbelleğe alınan bir sorgu kullanıcıya göre farklı sonuç üretiyorsa anahtarın yalnız sorgu parametresinden oluşması güvenli değildir. Aynı kimlik numarasına ilişkin iki farklı yetki kapsamı farklı alanlar görebilir. Anahtar belirlenirken veri kümesinin sürümü, yetki kapsamı ve gerektiğinde dil gibi çıktıyı değiştiren boyutlar açıkça değerlendirilir. Erişim hakkının geri alınmasıyla önbellek kaydının geçersizleşmesi arasındaki süre, doğrudan bir güvenlik gereksinimidir. Yalnız TTL kullanılması bu gereksinimin karşılandığını kanıtlamaz; yetki değişimiyle birlikte geçersizleştirme davranışı sınanmalıdır.

4.6. Mesajlaşma ve Asenkron İş Akışı

Kafka, AMQP/RabbitMQ ve benzeri broker entegrasyonları request-response zincirini ayırabilir. Mesajlaşma otomatik olarak güvenilirlik sağlamaz; delivery semantics, idempotent consumer, ordering, poison message ve dead-letter davranışı tasarlanmalıdır.

Bir DB transaction'ı ile broker publish işlemini iki bağımsız kaynak arasında atomikmiş gibi varsaymak hatalıdır. Outbox pattern gibi yöntemler, iş verisi ile publish niyetini aynı transaction'da kaydetmek için kullanılabilir.

4.7. Servislerde Kriptografik Anahtar ve Yapılandırma Sınırı

Bir Spring Boot servisinde kriptografik güvenlik, yalnız kitaplığın AES/GCM desteklemesiyle sağlanmaz. Anahtarın ortam değişkenine veya dosyaya nasıl yerleştirildiği, loglara sızıp sızmadığı, hangi yetkiyle okunabildiği ve yenilemenin eski kayıtlarla uyumu belirleyicidir. Servis konfigürasyonu; algoritma sürümü, anahtar referansı, nonce politikası ve farklı kiracıların güven sınırlarını açıklamalıdır. Şifreli kayıt kimliğinin AAD'ye bağlanması, iş kuralı doğrulamasının yerini almaz.

WebClient üzerinden kurulan TLS bağlantılarında sertifika doğrulaması devre dışı bırakılmamalı; güven deposu ve hedef ad eşleşmesi sürüm geçişlerinde test edilmelidir. Test verisindeki sabit anahtarlar üretim dağıtımına taşınmamalıdır. Kriptografik tasarım ilkeleri Kriptoloji dersinde, yapılandırmanın Spring yaşam döngüsü içindeki yürütme biçimi bu derste yer alır.

Ünite 5: Sunum, Test, Gözlemlenebilirlik ve Dağıtım

5.1. Thymeleaf ve Sunucu Taraflı HTML

Thymeleaf, server-side rendered HTML için geçerli bir çözümdür. Controller model üretir, template HTML çıktısını oluşturur. Bu yaklaşım ayrı SPA katmanı gerektirmeyen uygulamalarda dağıtım ve state modelini sadeleştirebilir.

Template'e entity grafiği taşımak yerine view model kullanmak daha güvenlidir. Kullanıcı verisini raw HTML olarak basmak XSS riskini artırır; template engine'in escaping davranışı bilinçli biçimde korunmalıdır.

5.2. Test Katmanları

Spring uygulamasında her testin @SpringBootTest olması gereksiz derecede pahalıdır. Test kapsamı ihtiyaca göre ayrılır:

  • saf birim testi: Spring context açılmaz,
  • MVC slice: @WebMvcTest,
  • JPA slice: @DataJpaTest,
  • entegrasyon: @SpringBootTest,
  • gerçek servis bağımlılığı: Testcontainers ve @ServiceConnection.

Boot 4 modüler test starter'ları sunar; kullanılan ana teknoloji starter'ının test karşılığı migration sırasında kontrol edilmelidir.

@WebMvcTest(OrderController.class)
class OrderControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @MockitoBean
    private OrderService service;

    @Test
    void missingOrderReturns404() throws Exception {
        when(service.find(42L)).thenThrow(new OrderNotFoundException(42L));
        mockMvc.perform(get("/api/orders/42"))
                .andExpect(status().isNotFound());
    }
}

Mock ile her şeyi taklit etmek, ORM mapping veya gerçek SQL davranışını test etmez. Persistence kritikse gerçek veritabanı motoruna yakın entegrasyon testi gerekir.

5.3. Actuator ve Gözlemlenebilirlik

Actuator; health, info, metrics, loggers ve diğer işletim uç noktalarını sağlar. Uç nokta açmak ile internete yayınlamak aynı şey değildir. Exposure ve authorization ayrı yapılandırılmalıdır.

Spring Boot gözlemlenebilirlik modelinde log, metric ve trace üçlüsünü; metric ve trace tarafında Micrometer Observation altyapısını kullanır. Cardinality kontrolü üretim maliyetinin parçasıdır. Kullanıcı kimliği, request ID veya serbest metin gibi yüksek cardinality alanları metric tag olarak kontrolsüz kullanılmamalıdır.

Uygulamanın health cevabı da doğru modellenmelidir. Process'in ayakta olması, dış bağımlılıkların sağlıklı olduğu anlamına gelmez; buna karşılık her dış bağımlılığı liveness'a bağlamak zincirleme restart üretebilir.

5.4. Paketleme ve Dağıtım

Executable JAR, modern Spring Boot servisleri için varsayılan paketleme yaklaşımıdır. WAR, harici servlet container gerektiren kurumsal ortamlarda hâlâ mümkündür; ancak ayrı container yönetimi zorunluluk değilse executable uygulama daha basit bir operasyon modeline sahiptir.

Container image oluştururken:

  • immutable artefakt,
  • non-root process,
  • read-only filesystem ihtiyacı,
  • JVM heap/container memory oranı,
  • graceful shutdown,
  • readiness/liveness ayrımı,
  • SBOM ve dependency provenance

düşünülmelidir.

Boot build plugin'leri OCI image üretimini buildpack ile destekler.

5.5. GraalVM Native Image ve AOT

Spring Boot, GraalVM Native Image üretebilir. Native executable genellikle daha hızlı başlangıç ve daha küçük runtime memory footprint sağlar; bunun karşılığında build süresi, closed-world varsayımı ve reflection/resource üst veri gereksinimleri artar.

Native image seçimi ölçümle yapılmalıdır. Uzun ömürlü, yüksek throughput JVM servisi JIT optimizasyonundan yararlanabilir; kısa ömürlü veya hızlı cold-start gerektiren workload native image'dan daha fazla fayda görebilir.

Reflection, dynamic proxy, resource loading ve serialization gibi dinamik özellikler AOT sırasında reachability hint gerektirebilir.

Ünite 6: Performans, Güvenilirlik ve Sürüm Geçişi

6.1. Performans: Framework Ayarından Önce Darboğazı Bulmak

Geliş oranı servis oranına yaklaştıkça kuyruk ve gecikmenin büyümesini Little yasası ve kullanım oranıyla gösteren kuyruk modeli
Kuyruk doygunluğu ve Little yasası

Bir Spring Boot servisinin gecikmesi çoğu zaman framework dispatcher maliyetinden değil şu katmanlardan gelir:

DB sorgusu
uzak servis RTT
connection pool bekleme
serialization
lock contention
GC/allocation
thread veya event-loop kuyrukları

Bu nedenle optimizasyon sırası ölçüm odaklı olmalıdır. Request başına gereksiz entity graph, limitsiz listeler, kontrolsüz retry ve büyük JSON gövdeleri öncelikle ele alınır.

Virtual iş parçacığı, reactive stack veya native image mimari borcu otomatik çözmez. Her biri farklı bir darboğaz sınıfına yönelik araçtır.

Bir ayar değişikliğinin ispat yükü. Varsayılan connection-pool boyutunu, JSON mapper ayarını veya HTTP sıkıştırmasını değiştirmek tek başına optimizasyon değildir. Hipotez, ölçülebilir mekanizmayı ifade etmelidir: örneğin “büyük JSON yanıtının ağdan aktarılma süresi baskın” önermesi için sunucu CPU süresi, serileştirilmiş byte miktarı, transfer süresi ve istemci tarafı ayrıştırma maliyeti birlikte ölçülür. Sıkıştırma ağ maliyetini düşürürken CPU maliyetini büyütebilir; kazanç yük ve veri dağılımına bağlıdır.

Deneyde kod dışındaki değişkenler sabitlenir; JVM warm-up tamamlanır, aynı veri kümesiyle karşılaştırma yapılır, hata oranı ve cevap semantiği korunur. Kazanç iddiası için yalnız ortalama süre değil p95/p99, kaynak başına işlem maliyeti ve kararlılık aralığı gösterilir. Deneyin uygulanmadığı durumda tahmini etki açıkça hipotez olarak kalmalıdır.

6.2. Güvenilirlik ve Hata Toleransı

300 istemcinin aynı anda başarısız olduktan sonra sabit gecikmeli ve üstel geri çekilmeli yeniden denemelerinin eşzamanlı dalgalar oluşturması, tam jitterin yükü yayarak tepe değeri azaltması
Yeniden deneme geri çekilme ve jitter

Timeout olmadan yapılan uzak çağrı kaynak tüketimini sınırsızlaştırabilir. Retry ise idempotent olmayan işlemde aynı siparişi veya ödemeyi iki kez üretebilir. Sağlam politika şu kavramları birlikte ele alır:

  • connect/read/overall timeout,
  • sınırlı retry ve backoff,
  • idempotency key,
  • bulkhead/concurrency sınırı,
  • circuit breaker gerekiyorsa kontrollü kullanım,
  • fallback'in veri doğruluğuna etkisi,
  • queue/geri basınç.

Fallback “hata yokmuş gibi” sahte başarı üretmemelidir; business sözleşmesine göre stale/partial sonuç gerçekten kabul ediliyorsa açık biçimde modellenmelidir.

6.3. Katmanlar ve Transaction Dışı Yan Etkiler

Tipik akış:

HTTP adapter -> application service -> domain -> repository / external adapter

Bu bir zorunlu paket şablonu değil, bağımlılık yönünü görünür kılan düşünme aracıdır. Domain kodunun HTTP, JPA veya JSON annotation'larına gereksiz bağımlılığı azaltılırsa test ve migration daha kolay olur.

Transaction içinde e-posta, HTTP veya broker çağrısı yapmak iki-phase commit olmadığı sürece ayrı hata alanlarını tek atomik işlem gibi gösterir. Yan etkiler ve kalıcı state değişikliği gerektiğinde outbox, event veya explicit orchestration ile ayrılmalıdır.

6.4. Boot 2/3 Kaynaklarını Boot 4'e Taşırken

Eski eğitim materyalindeki bilgi kavramsal olarak değerli olabilir; API ve paket adları güncellenmelidir. Başlıca kontrol noktaları:

  • javax.* yerine modern Jakarta paketleri,
  • spring-boot-starter-web yerine yeni projelerde spring-boot-starter-webmvc,
  • Jackson 3'e geçiş,
  • eski security adapter yapılandırmaları yerine SecurityFilterChain,
  • @EnableGlobalMethodSecurity yerine @EnableMethodSecurity,
  • yeni senkron HTTP istemcilerinde RestClient,
  • RestTemplate için deprecation/migration planı,
  • teknolojiye özel Boot 4 starter ve test starter'ları,
  • AOT/native image kullanılıyorsa dinamik yapıların yeniden kontrolü.

Migration'da ilk hedef “derleniyor” değil, davranış eşdeğerliğidir. Serialization, validation, security filter ordering, transaction ve DB migration davranışları regression test ile doğrulanmalıdır.

6.5. Spring Boot Servislerinde Kapasiteyi Zincir Olarak Modellemek

Spring Boot servisinin işleme kapasitesi HTTP istek kabulünden veri tabanı işleminin tamamlanmasına kadar uzanan bağımlılık zinciriyle belirlenir. Uygulama daha fazla eşzamanlı isteği kabul etse bile HikariCP havuzu, sorgu planı veya uzak hizmet kapasitesi sabit kalabilir. Bu nedenle connection acquisition time, transaction süresi ve yanıt kuyruğu ayrı ölçülmeli; bekleyen işin sınırsız büyümesine izin verilmeden timeout, cancellation ve kontrollü yük reddi politikaları birlikte sınanmalıdır.

  • HTTP worker/virtual-thread, connection pool ve DB concurrency sınırlarının birlikte modellenmesi.
  • Retry politikasının overload sırasında yükü katlayabilme etkisinin ölçülmesi.
  • Timeout değerlerinin servis süresi dağılımı ve downstream SLO ile ilişkilendirilmesi.

6.6. Virtual Thread, JDBC Havuzu ve Kabul Kontrolünün Birlikte Tasarımı

Sanal iş parçacıkları bloklayan Java isteklerinin zamanlama maliyetini azaltabilir; ancak JDBC bağlantısı, veri tabanı oturumu, eşzamanlı SQL yürütme kapasitesi ve uzak G/Ç sınırlarını genişletmez. Aynı anda binlerce virtual thread oluşturabilen bir servis, daha küçük HikariCP havuzunda bekleyen istek birikimi nedeniyle kuyruk gecikmesini artırabilir. Kapasite hesabı thread sayısına değil, bağlantı edinme süresi, transaction süresi ve veri tabanının sürdürülebilir işleme hızına dayanır.

Bağlantı edinimi için süre aşımı, request deadline ve admission control tek bir yük bütçesinin parçalarıdır. Büyük bağlantı havuzu her zaman yüksek throughput sağlamaz; veri tabanındaki contention veya I/O beklemeleri artabilir. Yük testlerinde active, idle ve pending bağlantılar, bağlantı edinme p95/p99, transaction süresi, hata oranı ve veritabanı bekleme olayları birlikte karşılaştırılır. Retry politikası, havuz doygunken ikinci bir talep dalgası oluşturmamalıdır.

Ünite 7: Uçtan Uca Uygulama ve Davranış Doğrulama

7.1. Uçtan Uca Küçük Servis Örneği

Aşağıdaki yapı HTTP sözleşmesini, application service'i ve repository'yi ayırır:

public record CreateDeviceRequest(@NotBlank String serialNumber) {
}

public record DeviceResponse(long id, String serialNumber) {
}
@Entity
@Table(name = "device")
class DeviceEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true, length = 64)
    private String serialNumber;

    protected DeviceEntity() {
    }

    DeviceEntity(final String serialNumber) {
        this.serialNumber = serialNumber;
    }

    Long id() {
        return id;
    }

    String serialNumber() {
        return serialNumber;
    }
}
interface DeviceRepository extends JpaRepository<DeviceEntity, Long> {
}
@Service
class DeviceService {
    private final DeviceRepository repository;

    DeviceService(final DeviceRepository repository) {
        this.repository = repository;
    }

    @Transactional
    DeviceResponse create(final CreateDeviceRequest request) {
        DeviceEntity entity = repository.save(new DeviceEntity(request.serialNumber()));
        return new DeviceResponse(entity.id(), entity.serialNumber());
    }
}
@RestController
@RequestMapping("/api/devices")
class DeviceController {
    private final DeviceService service;

    DeviceController(final DeviceService service) {
        this.service = service;
    }

    @PostMapping
    ResponseEntity<DeviceResponse> create(@Valid @RequestBody final CreateDeviceRequest request) {
        DeviceResponse response = service.create(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(response);
    }
}

Bu örnek bilinçli olarak caching, security ve uzak çağrı içermiyor. Her yeni altyapı bileşeni yalnız gerçek gereksinim varsa eklenmelidir.

7.2. İlgili Dersler

Spring Boot, Java Programlama dil ve JVM bilgisinin uygulama çatısı üzerindeki devamıdır. HTTP ve istemci-sunucu modeli için Web Programlama, persistence ve transaction için Veri Tabanı Yönetim Sistemleri, mimari ve kalite kararları için Yazılım Mühendisliği, dil ve çalışma zamanı karşılaştırmaları için Programlama Dilleri birlikte okunabilir.

7.3. Bloklayan Sınırlar, Transaction Kapsamı ve Uzak Çağrılar

@Transactional bir yöntemi atomik iş süreci hâline getirmez; yalnız ilgili transaction manager'ın yönettiği kaynaklarda transaction sınırı oluşturur. Aynı yöntem içinde veritabanı güncellemesi ile uzak HTTP çağrısı yapmak, iki sistemi tek ACID transaction'a dönüştürmez.

Daha önemlisi, transaction açıkken uzak servisi beklemek veritabanı bağlantısını ve muhtemel kilitleri gereksiz süre elde tutabilir. Ağ gecikmesi yükseldiğinde connection pool tükenmesi ve lock süresi birlikte büyüyebilir.

Yerel veri değişikliği ile dış yan etkinin tutarlı olması gerekiyorsa outbox, idempotent tüketici veya saga benzeri desenler değerlendirilebilir. Amaç her sistemi dağıtık transaction'a sokmak değil, kısmi hatanın hangi durumda nasıl toparlanacağını açıkça tasarlamaktır.

7.4. Spring Davranışını Framework Sözleşmesine Bağlamak

Spring Boot otomatik yapılandırması sürüme ve classpath'e bağlıdır. Bir bean'in “kendiliğinden” oluştuğu varsayılmamalı; condition evaluation ve resmi referans dokümanı üzerinden hangi koşulun etkin olduğu görülebilmelidir.

@Transactional, güvenlik filter chain'i veya HTTP istemci davranışı proxy/scope sınırlarına bağlıdır. Self-invocation, thread değişimi veya uzak servis çağrısı gibi durumlarda anotasyonun beklenen etkiyi üretip üretmediği ayrı doğrulanmalıdır.

Üretim testinde yalnız contextLoads yeterli değildir. Veritabanı, serialization, timeout, authorization ve hata sözleşmesi için gerçek protokole yakın integration testleri; kritik yapılandırma için de açık property/bean doğrulaması gerekir.

Ünite 8: Spring Container, Proxy ve Transaction Semantiğini Derinleştirmek

Spring Boot uygulamasının davranışını yalnız anotasyon listesiyle anlamak zordur. @Service, @Transactional, @Cacheable veya @Async gibi anotasyonların etkisi; bean'in yaşam döngüsü, proxy sınırı, çağrının hangi nesne üzerinden geçtiği ve hangi thread üzerinde devam ettiğiyle birlikte değerlendirilmelidir.

8.1. Bean Kapsamı (Scope) Yaşam Süresi Sözleşmesidir

Spring'de varsayılan bean kapsamı singletondır. Bu, Java dilindeki klasik singleton kalıbıyla tamamen aynı şey değildir. Anlamı kabaca:

İlgili ApplicationContext içinde bu bean tanımı için tek paylaşılan örnek.

Web uygulamalarında ayrıca request, session ve benzeri kapsamlar; genel container düzeyinde ise prototype gibi kapsamlar bulunabilir.

singleton
 -> context yaşamı boyunca paylaşılan örnek

prototype
 -> her çözümlemede yeni örnek

request
 -> HTTP isteği kapsamı

session
 -> HTTP oturumu kapsamı

Kapsam seçimi thread-safety sorununu otomatik çözmez. Paylaşılan singleton bean mutable request durumu tutuyorsa aynı örneğe eşzamanlı istekler erişebilir.

Bu nedenle tipik servis bean'leri mümkün olduğunca:

  • durumsuz,
  • immutable bağımlılıklı,
  • isteğe özel veriyi metot parametresinde taşıyan

yapıda tutulur.

8.2. Kısa Ömürlü Bean'i Uzun Ömürlü Bean'e Enjekte Etmek

Bir prototype bean doğrudan singleton constructor'ına verilirse, singleton oluşturulurken çözülmüş tek örnek uzun süre tutulabilir. "Prototype olduğu için her çağrıda yenilenir" varsayımı burada doğru değildir.

Gerçekten ihtiyaç anında yeni örnek gerekiyorsa:

singleton
   ↓
ObjectProvider / factory
   ↓ her ihtiyaçta
prototype instance

gibi gecikmeli çözümleme kullanılır.

Aynı sorun request/session kapsamlı nesnelerin daha uzun ömürlü nesnelere bağlanmasında scoped proxy veya provider ihtiyacını doğurabilir.

Bu, Dependency Injection'ın yalnız nesne bulma değil yaşam sürelerini uyumlu bağlama problemi olduğunu gösterir.

8.3. Bean Yaşam Döngüsü ve Kaynak Sahipliği

Spring singleton bean yaşam döngüsünde yapıcı, bağımlılık enjeksiyonu, Aware geri çağrıları, init geri çağrıları, vekil sarmalama ve kapanışta ters sırayla yok etme geri çağrıları
Spring bean yaşam döngüsü

Bir bean oluşturulduğunda genel akış:

instance creation
  ↓
dependency injection
  ↓
post-processors
  ↓
initialization callbacks
  ↓
runtime use
  ↓
destruction callbacks

şeklinde düşünülebilir.

@PostConstruct ağır ağ çağrısı, büyük veri yükleme veya bloklayan başlangıç işi için sorgusuz varsayılan olmamalıdır. Başlatma hatası bütün context'in açılmasını engelleyebilir.

Benzer biçimde @PreDestroy veya DisposableBean, uygulama kapanışında cleanup için yararlı olsa da process'in zorla sonlandırıldığı her durumda çalışacağı varsayılmamalıdır. Kalıcı veri doğruluğu yalnız shutdown callback'e bağlanmamalıdır.

8.4. Proxy Sınırı: Anotasyonun Çalıştığı Yer

Spring proxy üzerinden gelen çağrıda tavsiyenin çalışması ve this ile yapılan iç çağrıda proxy sınırının atlanması
Proxy sınırı ve iç çağrı

Spring AOP ve deklaratif transaction yönetimi çoğunlukla proxy üzerinden çağrı yakalar.

caller
  ↓
Spring proxy
  ↓
interceptor / advice
  ↓
target method

Çağrı proxy'den geçmezse ilgili advice çalışmayabilir.

Klasik öz-çağrı (self-invocation) örneği:

@Service
class BillingService {

    public void outer() {
        inner(); // aynı this üzerinden
    }

    @Transactional
    public void inner() {
        // ...
    }
}

outer() dışarıdan proxy üzerinden çağrılsa bile inner() çağrısı aynı hedef nesnenin this referansı üzerinden gerçekleşir. Bu nedenle inner() üzerindeki proxy tabanlı advice beklenen biçimde devreye girmeyebilir.

Çözüm çoğu zaman "proxy'yi elle bul" değildir. Transaction sınırını ayrı bir public use-case bean'ine taşımak sorumluluğu daha görünür kılar.

8.5. JDK Proxy ile Sınıf Tabanlı Proxy Farkı

Spring, bağlama göre interface tabanlı JDK proxy veya sınıf alt türüne dayalı proxy kullanabilir.

Bu ayrım şu davranışları etkileyebilir:

  • hangi metotların yakalanabildiği,
  • final metotların advice alıp alamaması,
  • görünürlük sınırları,
  • instanceof ve concrete type beklentileri.

Framework tarafından proxy'lenen bean'i "tam olarak yazdığım sınıf nesnesi" diye varsaymak yerine, dışarıdan gözlenen sözleşmeye göre kullanmak daha güvenlidir.

8.6. Transaction Yayılımı (Propagation)

Spring transaction propagation içinde REQUIRED ve REQUIRES NEW davranışlarının karşılaştırılması
Spring transaction propagation

@Transactional iç içe servis çağrılarında yeni transaction açıp açmama kararını yayılım semantiğiyle yönetir.

En sık kullanılan REQUIRED:

transaction varsa -> ona katıl
yoksa             -> yeni transaction aç

REQUIRES_NEW ise dış transaction'ı askıya alıp bağımsız yeni transaction isteyebilir.

Bu davranış:

outer tx
  ↓ suspend
inner new tx
  ↓ commit/rollback
outer tx resumes

gibi düşünülebilir.

REQUIRES_NEW "daha güvenli" değildir. Ayrı bağlantı ihtiyacı, farklı commit zamanı ve dış transaction geri alınsa bile iç işlemin kalıcı kalması gibi semantik sonuçları vardır.

NESTED ise savepoint benzeri davranışa dayanabilir ve kullanılan transaction yöneticisinin/altyapının desteğine bağlıdır. REQUIRES_NEW ile aynı kavram değildir.

Bildirimsel transaction yönetiminde anotasyonun varlığı tek başına işlem açıldığını kanıtlamaz; çağrının proxy üzerinden geçip geçmediği de belirleyicidir. Aynı nesnenin kendi yöntemini doğrudan çağırdığı self-invocation durumunda proxy kesişimi gerçekleşmeyebilir ve beklenen yayılım kuralı uygulanmayabilir. Doğrulama, yalnız başarısızlıkta istisna gözlemek değil, iki veri yazımından birinin hata verdiği kontrollü senaryoda her ikisinin de geri alınıp alınmadığını sınamaktır. Veritabanı transaction'ı ile uzak HTTP çağrısı tek atomik işlem sayılmamalı; uzak yan etkiler için ayrıca idempotency veya telafi sözleşmesi kurulmalıdır.

8.7. İzolasyon Seviyesi ve readOnly

Transaction yayılımı ile izolasyon aynı ayar değildir.

İzolasyon:

eşzamanlı transaction'lar
birbirlerinin hangi ara durumlarını
hangi garantilerle görür?

sorusunu ele alır.

readOnly = true ise veritabanından veritabanına ve sağlayıcıdan sağlayıcıya farklı optimizasyon/hint davranışı oluşturabilir; tek başına güvenlik duvarı değildir. "Bu transaction kesinlikle yazamaz" garantisi gerekiyorsa veritabanı yetkisi ve gerçek DBMS davranışı ayrıca değerlendirilmelidir.

8.8. Rollback Sözleşmesi

Spring'in varsayılan rollback davranışı checked ve unchecked istisna ailelerini aynı şekilde ele almaz. İş kuralı hatasının hangi istisna türüyle ifade edildiği transaction sonucunu değiştirebiliyorsa bu durum açık sözleşme olmalıdır.

Ayrıca istisnayı transaction içindeyken yakalayıp yutmak:

try {
    repository.save(entity);
} catch (RuntimeException ex) {
    log.warn("ignored", ex);
}

işlemin beklenmedik biçimde devam etmesine veya transaction'ın zaten rollback-only işaretlenmiş olmasına yol açabilir.

Hata yönetimi ile commit/rollback politikası birlikte tasarlanmalıdır.

8.9. Thread Sınırı Transaction Sınırıdır

Thread-local bağlama dayanan transaction bağlamı, yeni thread veya farklı yürütme modeline otomatik taşınmış varsayılmamalıdır.

Örneğin:

@Transactional method
  ↓
async task / başka thread

geçişinde ikinci aşamanın aynı transaction içinde olduğunu varsaymak güvenli değildir.

Aynı neden reaktif akışlarda da farklı context taşıma modellerini gündeme getirir. Servlet thread modeli, virtual thread ve reactive context aynı transaction taşıma mekanizması değildir.

8.10. Yapılandırma Önceliği ve Sahiplik

Aynı server.port anahtarının beş kaynakta tanımlı olması ve komut satırı değerinin 9090 ile diğerlerini gölgede bırakması; üstteki kaynak kalkınca bir sonraki görünür
Spring Boot özellik kaynağı önceliği

Spring Boot dış yapılandırmayı çok sayıda kaynaktan birleştirir. Bir değer:

  • varsayılan,
  • application dosyası,
  • profil,
  • environment,
  • system property,
  • command-line

gibi farklı katmanlardan gelebilir.

Sorun yalnız "hangi değer kazandı?" değil, bu değerin sahipliği kimde? sorusudur.

Üretim ayarını kaynak koda gömmek deploy-time değişikliği zorlaştırır; kritik bir doğruluk parametresini rastgele environment değişkenine bırakmak ise izlenebilirliği azaltabilir.

@ConfigurationProperties ilişkili ayarları tipli ve doğrulanabilir bir sözleşmede toplamak için uygundur.

8.11. Auto-configuration ile Kullanıcı Bean'i Arasındaki Sınır

Boot otomatik yapılandırması "framework gizlice her şeyi yapıyor" modeli değildir. Birçok yapılandırma:

classpath koşulu
property koşulu
mevcut bean koşulu
web application türü

gibi şartlara göre etkinleşir.

Uygulama kendi bean'ini tanımladığında bazı auto-configuration parçalarının geri çekilmesi bilinçli sahiplik aktarımıdır.

Bu nedenle startup hatasında yalnız stack trace'e değil:

hangi condition eşleşti?
hangi bean zaten vardı?
hangi property davranışı değiştirdi?

sorularına da bakılmalıdır.

8.12. Test Slice'ı, Bağlam Önbelleği ve Test Maliyeti

@WebMvcTest veya @DataJpaTest gibi test dilimleri (test slices), yalnız daha hızlı test yazma kısayolu değildir. Testin hangi Spring alt sistemini doğruladığını açıklar.

Bütün testleri @SpringBootTest yapmak:

  • context açılış süresini artırabilir,
  • ilgisiz bean hatalarını teste taşıyabilir,
  • başarısızlığın kök nedenini büyütebilir.

Spring test altyapısı benzer yapılandırmalı context'leri önbelleğe alabilir. Her testte context'i kirleten veya farklı dinamik yapılandırma üreten tasarım bu önbellek avantajını azaltır.

Hedef:

en küçük gerçekçi test kapsamı
+
kritik sınırda gerçek entegrasyon

olmalıdır.

8.13. Önbellek Soyutlaması Veri Tutarlılığı Politikasının Yerini Tutmaz

Geçersizleştirme sonrasında önbelleğe bayat veri yazılması adımlarının ve durum değişimlerinin gösterimi
Geçersizleştirme sonrasında önbelleğe bayat veri yazılması

@Cacheable bir metot sonucunu önbelleğe almayı kolaylaştırır; fakat şu soruları cevaplamaz:

anahtar hangi boyutları içeriyor?
değer ne zaman bayat sayılıyor?
write sonrası nasıl geçersizleşiyor?
yetki kapsamı anahtara dahil mi?
çoklu instance nasıl tutarlı kalıyor?
cache stampede nasıl sınırlandırılıyor?

Provider seçimi bu soruların yalnız bir bölümünü çözer.

Cache'i performans anotasyonu değil, okuma doğruluğu ve kabul edilen bayatlık sözleşmesi olarak düşünmek daha güvenlidir.

Spring Boot 4.1.1 güncel kararlı çizgide olsa da framework davranışı sürüme bağlıdır. Kaynaklardaki eski Spring örnekleri kavramı öğretmek için yararlı olabilir; güncel API ve destek aralığı için daima kullanılan Spring Boot ve Spring Framework sürümünün resmî dokümantasyonu esas alınmalıdır.

8.14. Transaction Sınırı ile Dış Etki Arasındaki Başarısızlık Penceresi

@Transactional ile korunan bir veritabanı güncellemesinin ardından uzak HTTP isteği ya da mesaj yayımlama işlemi yapılması, iki eylemi atomik bir bütün hâline getirmez. Veritabanı commit işlemi başarılı olup süreç mesaj gönderilmeden kapanabilir; tersine, mesaj gönderilip veritabanı rollback olabilir. Yalnızca yeniden deneme eklemek de problemi çözmez: tekrarlanan mesajın yan etkisi ikinci kez uygulanabilir. Bu durumda sistemin hangi tutarlılık sözleşmesini sunduğu açıkça belirtilmelidir.

Yerel transaction kapsamında bir iş kaydıyla birlikte outbox kaydı yazmak, veritabanı durumu ile yayımlama niyetini aynı atomik commit içine alır. Daha sonra bağımsız yayımlayıcı bekleyen kayıtları gönderir. Yayımlayıcı gönderimden sonra fakat gönderildi işaretinden önce kapanırsa aynı olay tekrar gönderilebilir. Bu nedenle tüketicide olay kimliğiyle idempotency veya eşdeğer bir tekilleştirme mekanizması gerekir. Outbox tek başına uçtan uca exactly-once yürütme garantisi değildir.

Spring uygulamasında transaction kapsamı proxy üzerinden yapılan çağrılara bağlıdır. Aynı nesnenin kendi @Transactional metodunu doğrudan çağırması beklenen advice yolundan geçmeyebilir. Ayrıca transaction kapanmadan yapılan dış çağrılar bağlantı ve kilit tutma süresini gereksiz artırır. Tasarım testi, commit öncesi ve sonrası süreç kesintilerini, tekrar yayımlamayı ve yinelenmiş tüketimi ayrı hata durumları olarak ele almalıdır. Şema değiştirme yetkisi bulunmayan ortamlarda yeni outbox tablosu varsayılmamalı; mevcut veri sözleşmesi içinde sağlanabilen güvenilirlik düzeyi açıkça sınırlandırılmalıdır.

8.15. İstek Bağlamı, Yetkilendirme ve Gecikmiş Asenkron İşler

Bir HTTP isteğinde yapılan yetki denetimi, daha sonra bağımsız çalışan tüm görevler için süresiz izin belgesi değildir. Kullanıcı, hedef kaynak ve işlem türü her kalıcı değişiklik sınırında doğrulanmalı; önceden alınmış bir bağlamın yeni rol ve izin durumunun yerine geçmesine izin verilmemelidir. SecurityContext veya istek kapsamındaki verinin başka iş parçacığına otomatik taşındığı varsayılmamalıdır. Taşınan bağlamın güvenilirliği ile işlemin güncel yetkisi ayrı sorulardır.

Spring'in imperative transaction yönetiminde işlem bağlamı varsayılan olarak iş parçacığına bağlıdır; yeni bir thread açılması transaction'ı kendiliğinden devralmaz. Reactive yürütmede ise transaction bağlamı farklı bir model üzerinden iletilir. Bir dış servisi çağırmak, uzun medya akışını beklemek veya kullanıcı girdisi almak için veritabanı transaction'ını açık tutmak bağlantı tutma süresini gereksiz artırır.

Asenkron tamamlanma sırasında hedef nesnenin sürümü değişmişse sonuç ya yeni bağlama göre tekrar doğrulanır ya da kontrollü biçimde geçersiz sayılır. Böylece geç gelen yanıtın daha yeni bir durumun üzerine yazılması engellenir.

8.16. Rol Kapsamlı API Sözleşmelerinde Güvenli Varsayılan ve Çakışma

Kullanıcıya ait tuş ataması gibi tercihlerde kayıt anahtarı, çalışma alanı ve komut kimliğini birbirinden ayıran bir sözleşme gereklidir. API bir çalışma alanının ayarlarını döndürürken erişimi olmayan başka alanın komutlarını gereksiz yere göndermemelidir. Ortak komutlar açıkça tanımlanabilir; varsayılanı atanmamış olan komutun kendiliğinden tetiklenmemesi gerekir. Çakışma çözümünde tek çalışma alanının kısıtı yüzünden farklı çalışma alanındaki geçerli atama silinmemelidir.

Bir API'nin güvenliği yalnız HTTP 200 yanıtıyla sınanmaz. Okuma, güncelleme ve rol değişimi arasında ters sırada tamamlanan istekler; yinelenen kaydetme; yetkisiz komut kimliği ve bulunmayan hedef kayıt ayrı sözleşme testlerine konu olmalıdır. Kalıcı atama ile istemcideki anlık etkin harita arasındaki fark, ayrı bir sürüm veya istek nesliyle yönetilebilir.

Kaynakça

  • Brendan Gregg. Systems Performance: Enterprise and the Cloud. Pearson Education, 2014.
  • Mor Harchol-Balter. Performance Modeling and Design of Computer Systems: Queueing Theory in Action. Cambridge University Press, 2013.
  • Oracle, Java SE Downloads and Release Information. https://www.oracle.com/java/technologies/downloads/
  • Spring Framework. Bean Scopes. https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html
  • Spring Framework. Transaction Propagation. https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/tx-propagation.html
  • Spring, GraalVM Native Images with Spring Boot. https://docs.spring.io/spring-boot/how-to/native-image/
  • Spring, Spring Boot JSON Support — Jackson 3. https://docs.spring.io/spring-boot/reference/features/json.html
  • Spring, Spring Boot Observability. https://docs.spring.io/spring-boot/reference/actuator/observability.html
  • Spring, Spring Boot Reference Documentation — 4.1.x. https://docs.spring.io/spring-boot/
  • Spring, Spring Boot System Requirements. https://docs.spring.io/spring-boot/system-requirements.html
  • Spring, Spring Boot Testcontainers and Service Connections. https://docs.spring.io/spring-boot/reference/testing/testcontainers.html
  • Spring, Spring Data JPA Reference Documentation. https://docs.spring.io/spring-data/jpa/reference/
  • Spring, Spring Framework Reference Documentation — 7.0.x. https://docs.spring.io/spring-framework/reference/
  • Spring, Spring Security Reference — Method Security. https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html
  • Spring. Spring Boot 4.1.1 System Requirements. https://docs.spring.io/spring-boot/system-requirements.html
  • IETF. RFC 9846, TLS 1.3, 2026. https://www.rfc-editor.org/info/rfc9846
İçindekiler