Backend/๐ŸŒฑ Spring

[Spring] Layered Architecture

HS0601 2025. 7. 26. 09:44
Layered Architecture
๋ณต์žกํ•œ ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ๊ตฌ์กฐ์ ์œผ๋กœ ๋‚˜๋ˆ ์„œ ์œ ์ง€๋ณด์ˆ˜์™€ ํ™•์žฅ, ํ…Œ์ŠคํŠธ, ํ˜‘์—…์„ ๋” ์‰ฝ๊ฒŒ ํ•˜๋ ค๊ณ  ๋งŒ๋“ค์–ด์ง„ ์„ค๊ณ„ ๋ฐฉ์‹์ด๋‹ค.

์ฝ”๋“œ ์—‰ํ‚ด(Spaghetti code)๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ž…๋ ฅ/์ฒ˜๋ฆฌ/์ €์žฅ/์ถœ๋ ฅ ๋“ฑ์„ ๊ฐ ๋‹จ๊ณ„๋กœ ์—ญํ• ๋ณ„๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ตฌ์„ฑํ•œ๋‹ค. 
์ด์ ์œผ๋กœ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค
1) ์ž…๋ ฅ,์ฒ˜๋ฆฌ, ์ถœ๋ ฅ ๋กœ์ง์„ ๋‚˜๋ˆ ์„œ ๋ณต์žก๋„๋ฅผ ๋‚ฎ์ถค
2) ๊ธฐ๋Šฅ ์ถ”๊ฐ€๋‚˜ ์ˆ˜์ • ์‹œ ์˜ํ–ฅ ๋ฒ”์œ„๊ฐ€ ์ค„์–ด๋“ฆ
3) ๊ฐ ๋ ˆ์ด์–ด ๋‹จ์œ„๋กœ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•จ
4) ๊ณตํ†ต ๋กœ์ง์„ Service๋‚˜ Repository๋กœ ๋ถ„๋ฆฌํ•ด์„œ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•จ
5) UI, DB ๋“ฑ ํŠน์ • ๋ ˆ์ด์–ด ๊ธฐ์ˆ ์„ ๋ฐ”๊พธ๊ธฐ ์‰ฌ์›€(์˜ˆ: JDBC->JPA)

์ฐธ๊ณ ๋กœ ๋ฐ์ดํ„ฐ ํ๋ฆ„์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
ํด๋ผ์ด์–ธํŠธ ↔ (HTTP) ↔ Controller→ Service→ Repository→ Database

 

 

์ดํ•ด๋ฅผ ๋•๊ธฐ ์œ„ํ•œ ์˜ˆ์‹œ

 

๋ ˆ์Šคํ† ๋ž‘ ์ฃผ๋ฌธ

 

[์—ญํ• ์— ๋”ฐ๋ฅธ ๋ถ„๋ฅ˜]
๊ณ ๊ฐ
์›จ์ดํ„ฐ - Controller
์ฃผ๋ฐฉ์žฅ - Service
์ฐฝ๊ณ  - Repository
์ ‘์‹œ/๋ฐ•์Šค - Response

 

  1. ๊ณ ๊ฐ, ์›จ์ดํ„ฐ, ์ฃผ๋ฐฉ์žฅ, ์ฐฝ๊ณ , ์ ‘์‹œ/๋ฐ•์Šค๊ฐ€ ์กด์žฌํ•œ๋‹ค.
  2. ๊ณ ๊ฐ์ด "ํ”ผ์ž ํ•œ ํŒ ์ฃผ์„ธ์š”" ์ฃผ๋ฌธ. ->์›น ๋ธŒ๋ผ์šฐ์ €,์•ฑ์—์„œ HTTP์š”์ฒญ
  3. ์ฃผ๋ฌธ์„œ๋ฅผ ๋ฐ›๊ณ  ์ฃผ๋ฐฉ์žฅ์—๊ฒŒ ์ „๋‹ฌ -> @Controller/ @RestController
  4. ๋ ˆ์‹œํ”ผ๋Œ€๋กœ ํ”ผ์ž๋ฅผ ๋งŒ๋“ค๊ณ  ์†Œ์Šค, ํ† ํ•‘์„ ์ค€๋น„ํ•œ๋‹ค -> ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง(๊ฒ€์ฆ, ๊ณ„์‚ฐ, ํŠธ๋žœ์žญ์…˜)
  5. ๋ฐ€๊ฐ€๋ฃจ, ์น˜์ฆˆ ๊ฐ™์€ ์žฌ๋ฃŒ๋ฅผ ๊บผ๋‚ด๋Š” ๊ณณ -> DB์—์„œ Entity ๊บผ๋‚ด๊ธฐ, ์ €์žฅํ•˜๊ธฐ
  6. ์†๋‹˜์—๊ฒŒ ์Œ์‹์„ ๋‹ด์•„ ๋‚ด์˜ค๋Š” ๊ทธ๋ฆ‡, ํฌ์žฅ๋ฐ•์Šค  -> Viewํ…œํ”Œ๋ฆฟ ๋ Œ๋”๋ง ๋˜๋Š” JSON์‘๋‹ต

 

์œ„ ์ˆœ์„œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋” ์ž์„ธํžˆ ๋น—๋Œ€์–ด ๋ณด๊ฒ ๋‹ค.

 

1. ๊ณ ๊ฐ์ด ์ฃผ๋ฌธ์„œ (Request DTO)๋ฅผ ์ž‘์„ฑ

{ "menu": "ํ”ผ์ž", "size": "L", "toppings": ["์น˜์ฆˆ","ํŽ˜ํผ๋กœ๋‹ˆ"] }

 

2. ์›จ์ดํ„ฐ(Controller)๊ฐ€ ์ฃผ๋ฌธ์„œ๋ฅผ ๋ฐ›์Œ

๋งค์žฅ ์‹์‚ฌ์šฉ์ด๋ฉด ๊ทธ๋ฆ‡์— ๋‹ด์•„์คŒ -> ๋ทฐ ๋ฐ˜ํ™˜

ํฌ์žฅ ์ฃผ๋ฌธ์ด๋ฉด ๋ฐ•์Šค์— ๋‹ด์•„์คŒ-> JSON๋ฐ˜ํ™˜

 

3. ์›จ์ดํ„ฐ๊ฐ€ ์ฃผ๋ฐฉ์žฅ(Service)์—๊ฒŒ

"์ด ์†๋‹˜ ํ”ผ์ž ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”"

 

4. ์ฃผ๋ฐฉ์žฅ(Service)์€

์žฌ๋ฃŒ๊ฐ€ ์ถฉ๋ถ„ํ•œ์ง€ ํ™•์ธ(Validation)

์ฐฝ๊ณ (Repository)์—์„œ ๋ฐ€๊ฐ€๋ฃจ์™€ ์น˜์ฆˆ๋ฅผ ๊บผ๋‚ด๊ณ 

๋ ˆ์‹œํ”ผ(๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง)์— ๋”ฐ๋ผ ํ”ผ์ž ์™„์„ฑ.

๋งŒ๋“ค์–ด์ง„ ํ”ผ์ž๋ฅผ ๋‹ค์‹œ ์›จ์ดํ„ฐ์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค.

 

5. ๋งค์žฅ ์‹์‚ฌ๋ฉด ํ…œํ”Œ๋ฆฟ ์ด๋ฆ„์„ ์•Œ๋ ค์ฃผ๊ฑฐ๋‚˜ 

ํฌ์žฅ ์ฃผ๋ฌธ์ด๋ฉด JSON์œผ๋กœ ํฌ์žฅ ๋ฐ•์Šค์— ์‹ค์–ด ์†๋‹˜์—๊ฒŒ ๋Œ๋ ค์ค€๋‹ค.

 

 

์œ„ ์˜ˆ์‹œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฝ”๋“œ์— ๋Œ€์ž…ํ•ด ๋ณด๊ฒ ๋‹ค.

 

 


// 1) ์›จ์ดํ„ฐ(Controller)
@RestController              // “ํฌ์žฅ ์ฃผ๋ฌธ ์ „๋ฌธ” (@Controller + @ResponseBody)
@RequestMapping("/orders")    // /orders/** ๊ธฐ๋ณธ ๊ฒฝ๋กœ
public class OrderController {
    private final OrderService svc;
    public OrderController(OrderService svc) { this.svc = svc; }

    @PostMapping                // ์†๋‹˜์ด POST /orders ๋กœ ์ฃผ๋ฌธ์„œ ์ „์†ก
    public OrderResponseDto order(
        @RequestBody OrderRequestDto req  // JSON ์ฃผ๋ฌธ์„œ → OrderRequestDto
    ) {
        return svc.placeOrder(req);      // ์ฃผ๋ฐฉ์žฅ์—๊ฒŒ ์ฃผ๋ฌธ์„œ ๊ฑด๋„ค๊ณ  ๊ฒฐ๊ณผ ๋ฐ›์Œ
    }
}

 

// 2) ์ฃผ๋ฐฉ์žฅ(Service)
@Service
@Transactional
public class OrderService {
    private final IngredientRepo repo;
    public OrderService(IngredientRepo repo) { this.repo = repo; }

    public OrderResponseDto placeOrder(OrderRequestDto req) {
        // 2-1) ์žฌ๋ฃŒ ์ฐฝ๊ณ ์—์„œ ํ•„์š”ํ•œ ์žฌ๋ฃŒ ๊บผ๋‚ด๊ธฐ
        Ingredient dough = repo.findByName("๋ฐ€๊ฐ€๋ฃจ").get();
        // 2-2) ๋ ˆ์‹œํ”ผ๋Œ€๋กœ ํ”ผ์ž ๋งŒ๋“ค๊ธฐ(๊ฒ€์ฆ·๊ณ„์‚ฐ ๋“ฑ)
        Pizza pizza = new Pizza(dough, req.getToppings());
        // 2-3) ํ”ผ์ž ์™„์„ฑ → ์†๋‹˜์—๊ฒŒ ์ „๋‹ฌํ•  DTO ์ƒ์„ฑ
        return new OrderResponseDto("์ฃผ๋ฌธ ์™„๋ฃŒ!", pizza.getSize());
    }
}

 

// 3) ์ฐฝ๊ณ (Repository)
@Repository
public interface IngredientRepo extends JpaRepository<Ingredient, Long> {
    Optional<Ingredient> findByName(String name);
}

 

์ด๋•Œ ์š”์ ์œผ๋กœ๋Š”

1. Controller(์›จ์ดํ„ฐ)

์ฃผ๋ฌธ์„œ ๋ฐ›๊ธฐ -> Service์— ์ „๋‹ฌ -> ๊ฒฐ๊ณผ ๋ฐ›์•„์„œ ์†๋‹˜์—๊ฒŒ ๋Œ๋ ค์คŒ

@RestController๋ฉด ์ž๋™์œผ๋กœ JSONํฌ์žฅ ๋ฐ•์Šค์— ๋‹ด์•„์คŒ

 

2. Service(์ฃผ๋ฐฉ์žฅ)

์žฌ๋ฃŒ ํ™•์ธ, ๊ฒ€์ฆ, ์กฐ๋ฆฌ(๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง) -> ์•ˆ์ „ํ•˜๊ฒŒ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ

 

3. Repository (์ฐฝ๊ณ )

DB์—์„œ Entity(์žฌ๋ฃŒ) ๊บผ๋‚ด์ฃผ๊ธฐ, ๋ณด๊ด€ํ•˜๊ธฐ

 

4. DTO vs Entity

DTO: ์ฃผ๋ฌธ์„œ, ๋ฐ•์Šค(Controller <-> Service)

Entity: ์ฐฝ๊ณ  ์† ์‹ค์ œ ์žฌ๋ฃŒ(Service <-> Repository)

 


 

 

์ดํ•ด๊ฐ€ ๋์œผ๋‹ˆ ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์ด์–ด๊ฐ€ ๋ณด๊ฒ ๋‹ค.

์ „์ฒด ์•„ํ‚คํ…์ฒ˜ ๊ฐœ์š”
ํด๋ผ์ด์–ธํŠธ ↔ (HTTP) ↔ Controller → Service → Repository → Database
๊ตฌํ˜„ ๋กœ๋“œ๋งต (๋‹จ๊ณ„๋ณ„ ์ˆœ์„œ)
1) DTO ์ •์˜ (Controller ↔ Service)
2) Entity ์ •์˜ (Service ↔ Repository)
3) Repository ์ธํ„ฐํŽ˜์ด์Šค ์ž‘์„ฑ
4) Service ๋กœ์ง ๊ตฌํ˜„
5) Controller ์—”๋“œํฌ์ธํŠธ ๊ตฌํ˜„

 

์œ„ ๊ตฌํ˜„ ๋กœ๋“œ๋งด์— ๋”ฐ๋ผ ์ˆœ์„œ๋Œ€๋กœ ์„ค๋ช…ํ•˜๊ณ ์ž ํ•œ๋‹ค.

 

1) DTO ์ •์˜ (Controller ↔ Service)

 

// src/main/java/com/example/demo/dto/OrderRequestDto.java
package com.example.demo.dto;

/**
 * ํด๋ผ์ด์–ธํŠธ(๊ณ ๊ฐ)๊ฐ€ ์„œ๋ฒ„์— ๋ณด๋‚ด๋Š” ์ฃผ๋ฌธ์„œ DTO
 */
public record OrderRequestDto(
    String menu,         // ์ฃผ๋ฌธํ•  ์ƒํ’ˆ๋ช…, e.g. "ํ”ผ์ž"
    String size,         // ๋ฉ”๋‰ด ํฌ๊ธฐ, e.g. "L"
    List<String> toppings // ์ถ”๊ฐ€ ํ† ํ•‘ ๋ชฉ๋ก
) {}

 

// src/main/java/com/example/demo/dto/OrderResponseDto.java
package com.example.demo.dto;

/**
 * ์„œ๋ฒ„(์ฃผ๋ฐฉ)๊ฐ€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋Œ๋ ค์ฃผ๋Š” ์‘๋‹ต DTO
 */
public record OrderResponseDto(
    String status,       // ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€, e.g. "์ฃผ๋ฌธ ์™„๋ฃŒ!"
    String size          // ์™„์„ฑ๋œ ํ”ผ์ž ํฌ๊ธฐ ํ™•์ธ์šฉ
) {}

 

  • OrderRequestDto: Controller๊ฐ€ ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ JSON์„ ๋งคํ•‘
  • OrderResponseDto: Service ๊ฒฐ๊ณผ๋ฅผ Controller๊ฐ€ JSON์œผ๋กœ ๋ฐ˜ํ™˜

 

2) Entity ์ •์˜ (Service ↔ Repository)

// src/main/java/com/example/demo/entity/Ingredient.java
package com.example.demo.entity;

import jakarta.persistence.*;

/**
 * ์ฐฝ๊ณ (Repository)์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ์‹ค์ œ ์žฌ๋ฃŒ ์—”ํ‹ฐํ‹ฐ
 * - DB์˜ ingredients ํ…Œ์ด๋ธ”๊ณผ 1:1 ๋งคํ•‘๋จ
 */
@Entity
public class Ingredient {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;            // PK

    private String name;        // ์žฌ๋ฃŒ๋ช…, ์˜ˆ: "๋ฐ€๊ฐ€๋ฃจ", "์น˜์ฆˆ"
    private int quantity;       // ์žฌ๊ณ  ์ˆ˜๋Ÿ‰

    // JPA ์ŠคํŽ™์ƒ ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•œ ๊ธฐ๋ณธ ์ƒ์„ฑ์ž (protected ๊ถŒ์žฅ)
    protected Ingredient() {}

    /**
     * ์‹ ๊ทœ ์žฌ๊ณ  ์ถ”๊ฐ€·์ƒ์„ฑ์šฉ ์ƒ์„ฑ์ž
     * @param name ์žฌ๋ฃŒ๋ช…
     * @param quantity ์ดˆ๊ธฐ ์žฌ๊ณ  ์ˆ˜๋Ÿ‰
     */
    public Ingredient(String name, int quantity) {
        this.name = name;
        this.quantity = quantity;
    }

    // ————— ์ ‘๊ทผ์ž ๋ฉ”์„œ๋“œ —————
    public Long getId() {
        return id;
    }
    public String getName() {
        return name;
    }
    public int getQuantity() {
        return quantity;
    }

    // ————— ๋„๋ฉ”์ธ ๋กœ์ง ๋ฉ”์„œ๋“œ —————
    /**
     * ์žฌ๊ณ ๋ฅผ ์ฐจ๊ฐํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”์„œ๋“œ
     * @param amount ์ฐจ๊ฐํ•  ์ˆ˜๋Ÿ‰
     * @throws IllegalArgumentException ์žฌ๊ณ ๊ฐ€ ๋ถ€์กฑํ•˜๋ฉด ์˜ˆ์™ธ ๋ฐœ์ƒ
     */
    public void decrease(int amount) {
        if (amount < 0 || amount > this.quantity) {
            throw new IllegalArgumentException("์žฌ๊ณ ๊ฐ€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ์ˆ˜๋Ÿ‰: " + this.quantity);
        }
        this.quantity -= amount;
    }
}

 

  • ์ด ํด๋ž˜์Šค๋Š” Service์—์„œ ๊บผ๋‚ด์˜จ Ingredient ๊ฐ์ฒด๋ฅผ ๊ฐ€์ง€๊ณ 
    ์‹ค์ œ ์žฌ๊ณ ๋ฅผ ๊ฒ€์ฆ·์ฐจ๊ฐํ•˜๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง(๋„๋ฉ”์ธ ๋ฉ”์„œ๋“œ)์„ ํฌํ•จํ•œ๋‹ค.
  • JPA ์–ด๋…ธํ…Œ์ด์…˜(@Entity, @Id, @GeneratedValue)์œผ๋กœ
    DB ํ…Œ์ด๋ธ” ๋งคํ•‘ ์ •๋ณด๋ฅผ ์„ ์–ธํ•œ๋‹ค.


3) Repository ์ธํ„ฐํŽ˜์ด์Šค ์ž‘์„ฑ

// src/main/java/com/example/demo/repository/IngredientRepository.java
package com.example.demo.repository;

import com.example.demo.entity.Ingredient;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

/**
 * ์ฐฝ๊ณ (Repository) ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค
 * - JpaRepository๋ฅผ ํ™•์žฅํ•ด ๊ธฐ๋ณธ CRUD ๊ธฐ๋Šฅ ์ œ๊ณต
 * - ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋ฉ”์„œ๋“œ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋งŒ ์„ ์–ธํ•˜๋ฉด Spring Data JPA๊ฐ€ ๊ตฌํ˜„์ฒด ์ž๋™ ์ƒ์„ฑ
 */
@Repository
public interface IngredientRepository extends JpaRepository<Ingredient, Long> {
    /**
     * ์žฌ๋ฃŒ ์ด๋ฆ„์œผ๋กœ ๊ฒ€์ƒ‰
     * @param name ์žฌ๋ฃŒ๋ช…
     * @return ์žฌ๋ฃŒ ์—”ํ‹ฐํ‹ฐ Optional
     */
    Optional<Ingredient> findByName(String name);
}

 

  • JpaRepository<Ingredient, Long>
    → save(), findById(), findAll(), delete() ๋“ฑ CRUD ๋ฉ”์„œ๋“œ ๊ธฐ๋ณธ ์ œ๊ณต
  • findByName(String) ์‹œ๊ทธ๋‹ˆ์ฒ˜๋งŒ ์„ ์–ธํ•˜๋ฉด, Spring Data JPA๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ SELECT * FROM ingredient WHERE name = ? ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ด ์ค€๋‹ค.


4) Service ๋กœ์ง ๊ตฌํ˜„

// src/main/java/com/example/demo/service/OrderService.java
package com.example.demo.service;

import com.example.demo.dto.OrderRequestDto;
import com.example.demo.dto.OrderResponseDto;
import com.example.demo.entity.Ingredient;
import com.example.demo.repository.IngredientRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * ์ฃผ๋ฐฉ์žฅ(Service) ์—ญํ• ์„ ํ•˜๋Š” ํด๋ž˜์Šค
 * - @Service: ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ๋“ฑ๋ก
 * - @Transactional: ๋ฉ”์„œ๋“œ ์ „์ฒด๋ฅผ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ฌถ์–ด,
 *                   ์ค‘๊ฐ„์— ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ์ž๋™ ๋กค๋ฐฑ ์ฒ˜๋ฆฌ
 */
@Service
@Transactional
public class OrderService {

    private final IngredientRepository ingredientRepository;

    // ์ƒ์„ฑ์ž ์ฃผ์ž…: IngredientRepository ๋นˆ์ด ์ž๋™ ์ฃผ์ž…๋จ
    public OrderService(IngredientRepository ingredientRepository) {
        this.ingredientRepository = ingredientRepository;
    }

    /**
     * ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
     * 1) ๋„์šฐ ์žฌ๋ฃŒ ์กฐํšŒ ๋ฐ ์žฌ๊ณ  ์ฐจ๊ฐ
     * 2) ํ† ํ•‘ ์žฌ๋ฃŒ ์กฐํšŒ ๋ฐ ์žฌ๊ณ  ์ฐจ๊ฐ
     * 3) ์ฃผ๋ฌธ ์‚ฌ์ด์ฆˆ ํ™•์ธ (์ถ”๊ฐ€ ๊ณ„์‚ฐ·ํ• ์ธ ๋กœ์ง ๋“ฑ ํ™•์žฅ ๊ฐ€๋Šฅ)
     * 4) ์ฃผ๋ฌธ ์™„๋ฃŒ ๋ฉ”์‹œ์ง€ + ์‚ฌ์ด์ฆˆ๋ฅผ ๋‹ด์€ ์‘๋‹ต DTO ๋ฐ˜ํ™˜
     */
    public OrderResponseDto placeOrder(OrderRequestDto request) {
        // 1) ๋„์šฐ ์žฌ๋ฃŒ(๋ฐ€๊ฐ€๋ฃจ) ์กฐํšŒ → ์˜ˆ์™ธ ์ฒ˜๋ฆฌ → ์žฌ๊ณ  ์ฐจ๊ฐ
        Ingredient dough = ingredientRepository.findByName("๋ฐ€๊ฐ€๋ฃจ")
            .orElseThrow(() -> new IllegalArgumentException("๋„์šฐ ์žฌ๋ฃŒ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค: ๋ฐ€๊ฐ€๋ฃจ"));
        dough.decrease(1);
        ingredientRepository.save(dough);

        // 2) ์š”์ฒญ๋œ ๊ฐ ํ† ํ•‘๋งˆ๋‹ค ๋™์ผ ์ฒ˜๋ฆฌ
        for (String toppingName : request.toppings()) {
            Ingredient topping = ingredientRepository.findByName(toppingName)
                .orElseThrow(() -> new IllegalArgumentException("ํ† ํ•‘ ์žฌ๋ฃŒ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค: " + toppingName));
            topping.decrease(1);
            ingredientRepository.save(topping);
        }

        // 3) ์ฃผ๋ฌธ ํฌ๊ธฐ ํ™•์ธ (์—ฌ๊ธฐ์— ์ถ”๊ฐ€ ๋กœ์ง์„ ๋ถ™์ผ ์ˆ˜ ์žˆ์Œ)
        String size = request.size();

        // 4) ์ตœ์ข… ์‘๋‹ต DTO ์ƒ์„ฑ
        return new OrderResponseDto(
            "์ฃผ๋ฌธ ์™„๋ฃŒ! " + size + " ์‚ฌ์ด์ฆˆ ํ”ผ์ž ์ค€๋น„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
            size
        );
    }
}

 

  • @Service: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ณ„์ธต(Service Layer)์„ ๋‚˜ํƒ€๋ƒ„.
  • @Transactional: ๋ฉ”์„œ๋“œ๊ฐ€ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ์‹คํ–‰๋˜๋ฉฐ, ์ค‘๊ฐ„์— ์˜ˆ์™ธ๊ฐ€ ํ„ฐ์ง€๋ฉด ๋ชจ๋“  DB ๋ณ€๊ฒฝ์„ ๋กค๋ฐฑ.
  • ingredientRepository.findByName(...): ์žฌ๊ณ  ์กฐํšŒ → ์—†์œผ๋ฉด IllegalArgumentException ๋ฐœ์ƒ.
  • decrease(1): Ingredient ์—”ํ‹ฐํ‹ฐ์˜ ๋„๋ฉ”์ธ ๋ฉ”์„œ๋“œ๋กœ ์žฌ๊ณ  ์ฐจ๊ฐ, ๋ถ€์กฑ ์‹œ ์˜ˆ์™ธ ๋ฐœ์ƒ.
  • ingredientRepository.save(...): ๋ณ€๊ฒฝ๋œ ์žฌ๊ณ ๋ฅผ DB์— ๋ฐ˜์˜.
  • ๋ฐ˜๋ณต๋ฌธ ์•ˆ์—์„œ ์š”์ฒญ๋œ ๋ชจ๋“  ํ† ํ•‘๋„ ๋™์ผํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋ฉฐ, ์ดํ›„ OrderResponseDto๋ฅผ ๋งŒ๋“ค์–ด Controller์— ๋ฐ˜ํ™˜ํ•œ๋‹ค.


5) Controller ์—”๋“œํฌ์ธํŠธ ๊ตฌํ˜„

// src/main/java/com/example/demo/controller/OrderController.java
package com.example.demo.controller;

import com.example.demo.dto.OrderRequestDto;
import com.example.demo.dto.OrderResponseDto;
import com.example.demo.service.OrderService;
import org.springframework.web.bind.annotation.*;

/**
 * ์›จ์ดํ„ฐ(Controller) ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํด๋ž˜์Šค
 * - @RestController: JSON API ์ „์šฉ ์ปจํŠธ๋กค๋Ÿฌ
 * - @RequestMapping("/orders"): ๊ณตํ†ต URL ๊ธฐ๋ณธ ๊ฒฝ๋กœ ์„ค์ •
 */
@RestController
@RequestMapping("/orders")
public class OrderController {

    private final OrderService orderService;

    // ์ƒ์„ฑ์ž ์ฃผ์ž…: OrderService ๋นˆ์ด ์ž๋™์œผ๋กœ ์ฃผ์ž…๋จ
    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    /**
     * ์ฃผ๋ฌธ ์ƒ์„ฑ ์—”๋“œํฌ์ธํŠธ
     * POST /orders
     * - @RequestBody: ๋“ค์–ด์˜จ JSON ์š”์ฒญ ๋ฐ”๋””๋ฅผ OrderRequestDto๋กœ ๋งคํ•‘
     * - ๋ฐ˜ํ™˜๊ฐ’ OrderResponseDto๋Š” ์ž๋™์œผ๋กœ JSON ์ง๋ ฌํ™”๋˜์–ด ์‘๋‹ต ๋ฐ”๋””์— ๋‹ด๊น€
     */
    @PostMapping
    public OrderResponseDto createOrder(
        @RequestBody OrderRequestDto requestDto
    ) {
        // Service ๊ณ„์ธต์— ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ๋กœ์ง ์œ„์ž„ → ๊ฒฐ๊ณผ DTO ๋ฐ˜ํ™˜
        return orderService.placeOrder(requestDto);
    }

    /**
     * (์„ ํƒ) ์ „์ฒด ์žฌ๊ณ  ์กฐํšŒ ์˜ˆ์‹œ ์—”๋“œํฌ์ธํŠธ
     * GET /orders/ingredients
     * - ์š”์ฒญ ์—†์ด ํ˜ธ์ถœ ์‹œ, Service์—์„œ ์žฌ๊ณ  ๋ชฉ๋ก์„ ์กฐํšŒํ•ด ์˜ฌ ์ˆ˜ ์žˆ์Œ
     * - ์•„๋ž˜ ์˜ˆ์‹œ๋Š” Optional; ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•˜์ง€๋Š” ์•Š์Œ
     */
    // @GetMapping("/ingredients")
    // public List<IngredientDto> getAllIngredients() {
    //     return orderService.listAllIngredients();
    // }
}

 

 

  • @RestController
    → @Controller + @ResponseBody
  • @RequestMapping("/orders")
    → ์ด ํด๋ž˜์Šค ๋‚ด๋ถ€์˜ ๋ชจ๋“  ๋ฉ”์„œ๋“œ๋Š” /orders ๊ฒฝ๋กœ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋งคํ•‘
  • @PostMapping
    → HTTP POST ์š”์ฒญ(POST /orders)์„ ์ฒ˜๋ฆฌ
  • @RequestBody OrderRequestDto requestDto
    → ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๋‚ธ JSON ์ฃผ๋ฌธ์„œ๊ฐ€ OrderRequestDto๋กœ ์ž๋™ ๋ณ€ํ™˜
  • return orderService.placeOrder(requestDto)
    → Service์—์„œ ์ฒ˜๋ฆฌ๋œ OrderResponseDto๋ฅผ JSON์œผ๋กœ ์‘๋‹ต

 

 

Postman(๋˜๋Š” curl)์œผ๋กœ ํ…Œ์ŠคํŠธ

 

์ฃผ๋ฌธ ์ƒ์„ฑ (POST /orders)

  • URL: http://localhost:8080/orders
  • Method: POST
  • Header: Content-Type: application/json
  • Body (raw JSON):

 

{
  "menu": "ํ”ผ์ž",
  "size": "L",
  "toppings": ["์น˜์ฆˆ", "ํŽ˜ํผ๋กœ๋‹ˆ"]
}

 

์˜ˆ์ƒ ์‘๋‹ต (200 OK):

 

{
  "status": "์ฃผ๋ฌธ ์™„๋ฃŒ! L ์‚ฌ์ด์ฆˆ ํ”ผ์ž ์ค€๋น„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
  "size": "L"
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'Backend > ๐ŸŒฑ Spring' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Spring]์˜์กด๊ด€๊ณ„ ์ฃผ์ž…(DI)  (2) 2025.07.28
[Spring]Spring Bean  (2) 2025.07.28
[Spring] Mapping  (1) 2025.07.23
[Spring]MVC  (4) 2025.07.23
[Spring] Spring!  (1) 2025.07.22