Gün 2 / Web Layer & Runtime / Modül 05 — REST API Development Properly
Ana Portal
Modül 05 Gün 2 REST API ~80 dk

REST API Development Properly

Bu modülde sadece çalışan endpoint yazmayı değil, sürdürülebilir bir API contract tasarlamayı öğreneceğiz. Controller, DTO, validation, mapping ve HTTP status code kararlarını MiniBank üzerinde bir araya getirerek gerçek bir API katmanı oluşturacağız.

Anlatım
50 dk
MiniBank Case
25 dk
Kapanış
5 dk
Checkpoint
lab-05

Bir endpoint’in sadece “çalışmasını” değil, doğru contract ile dış dünyaya açılmasını sağlayacaksın.

1

Controller sorumluluğunu netleştireceksin

Controller’ın business logic yazılan yer değil, HTTP ile application layer arasındaki ince giriş kapısı olduğunu göreceksin.

2

DTO ve validation kullanacaksın

Entity’yi dışarı açmadan request/response contract’ı tasarlayacak, hatalı input’u kontrollü şekilde yakalayacaksın.

3

MiniBank API katmanını büyüteceksin

Account endpoint’lerini birlikte yazacak, Customer endpoint’lerini aynı pattern ile katılımcı görevi olarak tamamlayacaksın.

REST API konuşurken aynı kelimeleri aynı anlamda kullanalım.

REST

Basitçe: Kaynakları HTTP üzerinden temsil eden API yaklaşımıdır.

Neden önemli? URL, HTTP method ve status code kararlarını daha tutarlı vermemizi sağlar.

Resource

Basitçe: API’de dışarı açtığımız kavramsal varlıktır; örneğin account, customer, transfer.

Neden önemli? Endpoint tasarımında fiil değil kaynak odaklı düşünmemizi sağlar.

Endpoint

Basitçe: Client’ın çağırdığı URL + HTTP method birleşimidir.

Neden önemli? API contract’ın en görünür parçasıdır; değişirse client’ları etkiler.

DTO

Basitçe: Dış dünyaya veri taşımak için kullanılan sade objedir.

Neden önemli? Entity modelini API contract’tan ayırır.

Request DTO

Basitçe: Client’tan gelen veriyi temsil eder.

Neden önemli? Validation kuralları çoğunlukla burada görünür hale gelir.

Response DTO

Basitçe: Client’a dönen veriyi temsil eder.

Neden önemli? Entity içindeki hassas veya gereksiz alanları dışarı sızdırmadan cevap dönmemizi sağlar.

Validation

Basitçe: Gelen verinin kabul edilebilir olup olmadığını kontrol etmektir.

Neden önemli? Hatalı request’i business logic’e ulaşmadan durdurur.

HTTP Status Code

Basitçe: İsteğin sonucunu makine tarafından okunabilir şekilde anlatan koddur.

Neden önemli? Client’ın başarılı, hatalı, yetkisiz veya bulunamadı gibi durumları doğru yorumlamasını sağlar.

Mapping

Basitçe: Entity, domain object ve DTO arasında veri dönüştürme işlemidir.

Neden önemli? Katmanları birbirine yapıştırmadan API modeli üretmemizi sağlar.

REST API yazmak, controller’a birkaç annotation koymaktan daha fazlasıdır.

Spring Boot ile endpoint açmak kolaydır. Bir class’a @RestController, bir metoda @GetMapping koyduğunda ilk API’n çalışır. Ama senior seviyede asıl soru şudur: Bu API uzun vadede anlaşılır, güvenli, değiştirilebilir ve client dostu kalacak mı?

Bu modülün ana fikri

Controller HTTP dünyasını karşılar, DTO dış contract’ı temsil eder, validation yanlış input’u durdurur, service business işi yapar, response doğru status code ile döner.

5.1 REST API mental model

REST’i ilk aşamada karmaşık bir akademik tanım gibi düşünmeyelim. Bizim için REST, dış dünyanın MiniBank kaynaklarıyla HTTP üzerinden konuşmasıdır. Bir müşteri oluşturmak, bir hesabı görüntülemek, hesap listesini almak veya transfer başlatmak gibi işlemler birer API davranışıdır.

Yeni başlayan ekiplerde sık görülen hata, endpoint’leri method adı gibi tasarlamaktır: /createAccount, /getAccount, /deleteAccount. REST tarafında daha temiz yaklaşım ise kaynağı URL’de, yapılacak işi HTTP method’da anlatmaktır.

Daha zayıf yaklaşım

/createAccount
/getAccountById
/deleteAccount
/updateCustomer

URL fiil gibi davranır. API büyüdükçe isimlendirme dağılır.

Daha temiz yaklaşım

POST   /api/accounts
GET    /api/accounts/{id}
DELETE /api/accounts/{id}
PUT    /api/customers/{id}

URL kaynak, HTTP method aksiyon olur. Client için daha öngörülebilirdir.

Bu yaklaşım sadece estetik değildir. API contract net olduğunda frontend, mobil uygulama, başka microservice veya test otomasyonu bu API’yi daha kolay tüketir. Hatalı status code veya dağınık endpoint isimleri küçük projede sorun yaratmayabilir; ama servis sayısı arttıkça operasyonel maliyete dönüşür.

5.2 @RestController ne yapmalı, ne yapmamalı?

@RestController, gelen HTTP request’i Java metoduna bağlayan sınıftır. Ama controller business logic’in merkezi değildir. Controller’ın işi request’i almak, temel request mapping’i sağlamak, validation’ı tetiklemek, service’i çağırmak ve uygun response dönmektir.

01
Request gelir
URL, method, header ve body Spring MVC tarafından okunur.
02
DTO oluşur
JSON body request DTO’ya dönüştürülür.
03
Service çağrılır
Business operasyon controller dışında yürütülür.
04
Response döner
Response DTO ve status code ile cevap verilir.
Senior dikkat noktası

Controller içinde hesaplama, transaction kararı, repository çağrısı, external API çağrısı ve karmaşık if blokları birikiyorsa, controller yavaş yavaş application service gibi davranmaya başlamıştır. Bu ileride test ve bakım maliyetini artırır.

Temiz controller iskeleti
@RestController
@RequestMapping("/api/accounts")
public class AccountController {

    private final AccountApplicationService accountApplicationService;

    public AccountController(AccountApplicationService accountApplicationService) {
        this.accountApplicationService = accountApplicationService;
    }

    @PostMapping
    public ResponseEntity<AccountResponse> create(@Valid @RequestBody AccountCreateRequest request) {
        AccountResponse response = accountApplicationService.createAccount(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(response);
    }

    @GetMapping("/{accountNo}")
    public ResponseEntity<AccountResponse> getByAccountNo(@PathVariable String accountNo) {
        return ResponseEntity.ok(accountApplicationService.getAccount(accountNo));
    }
}

5.3 DTO Pattern: Entity dış contract değildir

DTO, API’nin dış dünyaya verdiği sözdür. Entity ise çoğunlukla veritabanı ve persistence modeliyle ilgilidir. Bu ikisini aynı obje üzerinden yönetmek başlangıçta hızlı görünebilir ama uzun vadede ciddi sorun üretir.

Örneğin AccountEntity içinde teknik alanlar, internal status değerleri, audit kolonları veya lazy relation’lar olabilir. Entity’yi doğrudan response olarak dönersen bu alanları istemeden dışarı açabilirsin. Ayrıca entity değiştiğinde API contract’ın da değişmiş olur. Bu, client tarafında beklenmeyen kırılmalara yol açar.

Entity’yi direkt dönmenin riskleri

  • Hassas alanlar dışarı sızabilir
  • Lazy relation serialization problemi çıkabilir
  • API contract persistence modeline bağımlı hale gelir
  • Entity değişikliği client kırabilir

DTO kullanmanın kazançları

  • API contract kontrollü olur
  • Request ve response modeli ayrılır
  • Validation daha okunur hale gelir
  • Client’a sadece gerekli veri döner
Request DTO ve Response DTO
public record AccountCreateRequest(
    @NotBlank(message = "Müşteri numarası boş olamaz")
    String customerNo,

    @NotBlank(message = "Para birimi boş olamaz")
    @Pattern(regexp = "TRY|USD|EUR", message = "Para birimi TRY, USD veya EUR olmalıdır")
    String currency
) {}

public record AccountResponse(
    String accountNo,
    String customerNo,
    String currency,
    BigDecimal balance,
    String status
) {}

5.4 Validation: Hatalı request business logic’e ulaşmadan durmalı

Validation iki seviyede düşünülmeli. Birinci seviye request’in şekli ve temel kurallarıdır: alan boş mu, format doğru mu, sayı pozitif mi, uzunluk uygun mu? Bunları request DTO üzerinde annotation ile ifade ederiz. İkinci seviye ise business validation’dır: müşteri gerçekten var mı, hesap aktif mi, transfer limiti yeterli mi? Bunlar service katmanında kalmalıdır.

01
JSON gelir
Client request body gönderir.
02
DTO oluşur
HttpMessageConverter JSON’u Java objesine çevirir.
03
@Valid çalışır
Bean Validation kuralları kontrol edilir.
04
Service’e geçer
Sadece geçerli request business logic’e ulaşır.

DTO validation

Teknik input kalitesini kontrol eder.

@NotBlank
@Size
@Pattern
@Positive
@Email

Business validation

İş kuralını kontrol eder.

Müşteri var mı?
Hesap aktif mi?
Bakiye yeterli mi?
Transfer limiti uygun mu?

@Validated ise özellikle service metotlarında method-level validation veya validation group kullanmak istediğimizde gündeme gelir. Bu modülde controller request validation’ı ana akışımız olacak; sonraki modüllerde business exception handling ile bunu tamamlayacağız.

MiniBank bağlantısı

Account oluştururken müşteri numarası ve para birimi request DTO üzerinde validate edilecek. Müşterinin gerçekten var olup olmadığı ise service katmanının iş kuralı olacak.

5.5 Proper HTTP Status Codes

Status code, API’nin client ile konuştuğu ortak dildir. Body içinde “success: false” yazmak tek başına yeterli değildir. Client önce HTTP seviyesindeki sonucu okur, sonra gerekirse body içindeki detaylara bakar.

200 OK

Başarılı okuma veya başarılı genel işlem.

201 Created

Yeni bir kaynak oluşturulduğunda tercih edilir.

204 No Content

Başarılı işlem var ama response body yok.

400 Bad Request

Request formatı veya validation hatalı.

404 Not Found

İstenen kaynak bulunamadı.

409 Conflict

İstek mevcut durumla çakışıyor; örneğin zaten kayıtlı kaynak.

Sık hata

Her hataya 500 dönmek client açısından “sunucu bozuldu” demektir. Oysa validation hatası 400, bulunamayan kaynak 404, iş kuralı çakışması 409 olabilir. Modül 06’da bu hata cevaplarını global exception handling ile standartlaştıracağız.

5.6 DTO Mapping: Manual mı MapStruct mı?

DTO kullandığımızda kaçınılmaz olarak mapping ihtiyacı doğar. Küçük projelerde manual mapping yeterince açık ve kontrol edilebilirdir. Daha büyük projelerde çok sayıda benzer mapping tekrarına düşüyorsak MapStruct gibi compile-time mapper araçları değerlendirilebilir.

Manual mapping

Küçük ve eğitim amaçlı senaryolarda okunur, bağımlılık gerektirmez, neyin nereye gittiği açıktır.

MapStruct

Çok sayıda DTO dönüşümü olan kurumsal projelerde boilerplate azaltır, compile-time mapping üretir.

Manual mapper örneği
@Component
public class AccountDtoMapper {

    public AccountResponse toResponse(Account account) {
        return new AccountResponse(
            account.accountNo(),
            account.customerNo(),
            account.currency(),
            account.balance(),
            account.status()
        );
    }
}

MiniBank eğitiminde ilk aşamada manual mapper kullanacağız. Çünkü amacımız mapping tool öğretmek değil, API contract ve katman ayrımını netleştirmek. İlerleyen aşamada istenirse MapStruct ayrı bir iyileştirme olarak eklenebilir.

MiniBank Case 05 — Account API, DTO & Validation

Bu case’de Account API katmanını birlikte kuracağız. Sonra katılımcılar aynı pattern’i Customer API için tamamlayacak. Görev listesi, doğrulama komutları, kod adımları ve kabul kriterleri Lab Dashboard'da.

Başlangıç
lab-05-start
Tamamlanmış
lab-05-complete
Checkpoint kuralı

Lab sırasında geride kalırsan lab-05-complete branch'ine geçip herkesle aynı checkpoint'ten devam edebilirsin.

Bu modülden akılda kalması gerekenler

1

REST API, endpoint açmaktan ibaret değildir.

URL, HTTP method, status code, validation ve response modeli birlikte API contract’ı oluşturur.

2

Controller ince kalmalıdır.

Controller HTTP akışını yönetir; business logic service/application layer’da kalmalıdır.

3

DTO, entity modelini dış dünyadan ayırır.

Request ve response DTO kullanmak API’yi daha güvenli ve daha stabil hale getirir.

4

Validation business logic’e ulaşmadan çalışmalıdır.

Temel input hataları DTO seviyesinde durdurulur, iş kuralları service katmanında ele alınır.

5

Status code client ile ortak dildir.

Doğru status code, API tüketicisinin sonucu doğru yorumlamasını sağlar.

5 kısa kontrol sorusu

1. Entity’yi neden doğrudan response olarak dönmek istemeyiz?

Çünkü entity persistence modelidir; hassas alanları, lazy relation’ları veya internal detayları dışarı sızdırabilir. DTO kullanmak API contract’ı kontrollü hale getirir.

2. Controller’ın ana sorumluluğu nedir?

HTTP request’i almak, DTO/validation akışını başlatmak, service’i çağırmak ve uygun response/status code dönmektir. Business logic controller’da birikmemelidir.

3. @Valid ne zaman çalışır?

Request body DTO’ya dönüştürüldükten sonra, controller metoduna geçmeden önce Bean Validation kurallarını tetikler.

4. Yeni kaynak oluşturulduğunda hangi status code tercih edilir?

Genellikle 201 Created tercih edilir. Response body içinde oluşturulan kaynağın bilgisi dönebilir.

5. DTO mapping için her zaman MapStruct kullanmak zorunda mıyız?

Hayır. Küçük ve eğitim odaklı senaryolarda manual mapping daha açık olabilir. Mapping tekrarları arttığında MapStruct gibi araçlar değerlendirilebilir.