Post

[Architecture] Layered Architecture

๐Ÿ’ก ๋ ˆ์ด์–ด๋“œ ์•„ํ‚คํ…์ณ๋ž€?


๋ ˆ์ด์–ด๋“œ ์•„ํ‚คํ…์ณ๋Š” ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์—ญํ• ๋ณ„๋กœ ์ˆ˜์ง ๊ณ„์ธต(layer) ์œผ๋กœ ๋‚˜๋ˆ„๋Š” ์„ค๊ณ„ ๋ฐฉ์‹์ด๋‹ค.
๊ฐ ๊ณ„์ธต์€ ์ž์‹ ์˜ ์ฑ…์ž„๋งŒ ์ˆ˜ํ–‰ํ•˜๋ฉฐ, ํ•˜์œ„ ๊ณ„์ธต์—๋งŒ ์˜์กดํ•˜๋Š” ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„๋‹ค.

์ด ๊ตฌ์กฐ๋Š” ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ(Separation of Concerns)๋ฅผ ํ†ตํ•ด
์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ, ์œ ์ง€๋ณด์ˆ˜์„ฑ, ์žฌ์‚ฌ์šฉ์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ๋ฐ ๋ชฉ์ ์ด ์žˆ๋‹ค.

๐Ÿ“š ์ฃผ์š” ๋ ˆ์ด์–ด ๊ตฌ์„ฑ (4๊ณ„์ธต)


4layered

์œ„ ๋ฐฉํ–ฅ์œผ๋กœ๋งŒ ์˜์กดํ•˜๋„๋ก ์„ค๊ณ„
์˜ˆ๋ฅผ ๋“ค์–ด, Repository๊ฐ€ Service ํ˜ธ์ถœโŒ

๊ณ„์ธต๋ณ„ ์„ค๋ช…

  1. Presentation Layer (Controller)
    • ์›น ์š”์ฒญ ์ˆ˜์‹ , ์š”์ฒญ ํ๋ฆ„ ์ œ์–ด
    • ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ๋ฐ›๊ณ  ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์—ญํ•  (Request, Response)
  2. Business Layer (Service)
    • ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ˆ˜ํ–‰
  3. Persistence Layer (Repository)
    • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ง์ ‘ ํ†ต์‹ ํ•˜๋Š” ๊ณ„์ธต (JPA, MyBatis ๋“ฑ)
  4. Database Layer (DB)
    • DB ํ…Œ์ด๋ธ”๊ณผ ๋งคํ•‘๋˜๋Š” ํ•ต์‹ฌ ๋„๋ฉ”์ธ ๊ฐ์ฒด

DTO (Data Transfer Object)

  • Controller โ‡Œ Service ๋˜๋Š” Service โ‡Œ ์™ธ๋ถ€ API ๊ฐ„ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ์šฉ
  • Entity๋ฅผ ์ง์ ‘ ๋…ธ์ถœํ•˜์ง€ ์•Š๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ

๐Ÿ’ป ๋„์„œ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ ์˜ˆ์‹œ


API ํ๋ฆ„

1
2
3
4
5
6
7
8
9
[ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ]
  โ†“์š”์ฒญ  โ†‘์‘๋‹ต
Controller (BookController)
  โ†“ํ˜ธ์ถœ  โ†‘๊ฒฐ๊ณผ
Service (BookService)
  โ†“ํ˜ธ์ถœ  โ†‘๊ฒฐ๊ณผ
Repository (BookRepository)
  โ†“์กฐํšŒ  โ†‘๊ฒฐ๊ณผ
Entity (Book)  โ‡Œ  DB Table (books)

์ฝ”๋“œ(์กฐํšŒ ๊ธฐ๋Šฅ) ์˜ˆ์‹œ

controller/BookController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RestController
@RequestMapping("/api/books")
public class BookController {
    private final BookService bookService;

    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    // 1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ "/books/{id}" ๋กœ Get ์š”์ฒญ
    // ?keyword=
    @GetMapping
    public ResponseEntity<List<BookDto>> searchBooks(
        @RequestParam(required = false) String keyword;
    ) {
        // ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์‘๋‹ต ์‹œ Dto ๋กœ ๋งคํ•‘
        List<BookDto> bookDtoList;

        // 2. Service ์กฐํšŒ ๊ธฐ๋Šฅ ํ˜ธ์ถœ
        if (keyword != null && !keyword.trim().isEmpty()) {
            bookDtoList = bookService.findByKeyword(keyword);
        } else {
            bookDtoList = bookService.findAll();
        }

        // ์‘๋‹ต
        return ResponseEntity.ok(bookDtoList);
    }
}

service/BookService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@service
public class BookService {
    private final BookRepository bookRepository;
    
    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    // 3. ์‹ค์ œ DB ์กฐํšŒ๋Š” Repository ์— ์œ„์ž„(ํ˜ธ์ถœ)
    public List<BookDto> findAll() {
        // ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ DTO ๋กœ ๋งคํ•‘ ํ›„ Controller ์—๊ฒŒ ๋ฐ˜ํ™˜
        return bookRepository.findAll()
                .stream()
                .map(BookDto::new)
                .toList();
    }

    public List<BookDto> findByKeyword(String keyword) {
        return bookRepository.findByTitleContainingIgnoreCase(keyword)
                .stream()
                .map(BookDto::new)
                .toList();
    }
}

repository/BookRepository.java

1
2
3
4
5
// 4. DB ์กฐํšŒ
@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
    List<Book> findByTitleContainingIgnoreCase(String keyword);
}

entity/Book.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Getter
@Entity
@Table(name = "books")
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String author;
    private String isbn;
    private String category;
    private LocalDateTime createdAt;

    public Book() {}

    public Book(String title, String author, String isbn, String category) {
        this.title = title;
        this.author = author;
        this.isbn = isbn;
        this.category = category;
        this.createdAt = LocalDateTime.now();
    }
}

dto/BookDto.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Getter
public class BookDto {
    private Long id;
    private String title;
    private String author;
    private String isbn;
    private String category;
    private String createdAt;

    public BookDto(Book book) {
        this.id = book.getId();
        this.title = book.getTitle();
        this.author = book.getAuthor();
        this.isbn = book.getIsbn();
        this.category = book.getCategory();
        this.createdAt = book.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
    }
}

โ“ ์™œ ๊ด€์‹ฌ์‚ฌ๋ฅผ ๋ถ„๋ฆฌํ•ด์•ผํ• ๊นŒ?


Presentation Layer(Controller) ์—์„œ ์ง์ ‘ DB ์— ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ์ด ํ›จ์”ฌ ๋น ๋ฅด๊ณ  ์ง๊ด€์ ์ผํ…๋ฐ,
์™œ ๊ด€์‹ฌ์‚ฌ๋ฅผ ๋ถ„๋ฆฌํ•ด์•ผํ• ๊นŒ?

1๏ธโƒฃ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์‰ฌ์›Œ์ง

๊ธฐ๋Šฅ๋ณ„๋กœ ์ฝ”๋“œ๊ฐ€ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์—,
์–ด๋А ๋ถ€๋ถ„์„ ์ˆ˜์ •ํ•ด์•ผ ํ•˜๋Š”์ง€ ๋ช…ํ™•ํ•˜๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์–ด
๋””๋ฒ„๊น…๊ณผ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ํŽธํ•ด์ง„๋‹ค.

2๏ธโƒฃ ๋ณ€๊ฒฝ์— ์œ ์—ฐํ•จ

์˜ˆ๋ฅผ ๋“ค์–ด

  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ MySQL โ†’ MongoDB ๋กœ ๋ฐ”๊ฟ€ ๋•Œ Repository ๋งŒ ์ˆ˜์ •
  • ์‘๋‹ต ํฌ๋งท์„ JSON โ†’ XML ๋กœ ๋ฐ”๊ฟ€ ๋•Œ Controller ๋งŒ ์ˆ˜์ •

์ฆ‰, ํ•œ ๋ถ€๋ถ„์„ ๋ฐ”๊ฟ”๋„ ๋‹ค๋ฅธ ๋ถ€๋ถ„์— ์˜ํ–ฅ์ด ์ ๋‹ค.

3๏ธโƒฃ ํ…Œ์ŠคํŠธ๊ฐ€ ์‰ฌ์›Œ์ง

  • ๊ฐ ๊ณ„์ธต์„ ๋…๋ฆฝ์ ์œผ๋กœ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์–ด์„œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์ด ์šฉ์ด
  • DB ์—†์ด๋„ Service ๊ณ„์ธต์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.

4๏ธโƒฃ ํ˜‘์—…์ด ํšจ์œจ์ 

  • ์—ญํ• ์ด ๋ถ„๋ฆฌ๋˜์–ด ์žˆ๊ณ , ๊ตฌ์กฐ๊ฐ€ ๋ช…ํ™•ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ˜‘์—…์— ์œ ๋ฆฌ
    • A๊ฐœ๋ฐœ์ž: Controller
    • B๊ฐœ๋ฐœ์ž: Repository
    • C๊ฐœ๋ฐœ์ž: Service

5๏ธโƒฃ ๊ณตํ†ต ๊ธฐ๋Šฅ ์ฒ˜๋ฆฌ์— ์œ ์—ฐ

๋กœ๊น…, ๋ณด์•ˆ, ํŠธ๋žœ์žญ์…˜ ๋“ฑ

6๏ธโƒฃ ์žฌ์‚ฌ์šฉ์„ฑ ์ฆ๊ฐ€

Service, Repository ๋“ฑ์˜ ๋กœ์ง์„ ๋‹ค๋ฅธ ๊ณณ์—์„œ ์žฌํ™œ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

๐Ÿ“ ์ •๋ฆฌ

์ด์ฒ˜๋Ÿผ ๊ด€์‹ฌ์‚ฌ๋ฅผ ๋ถ„๋ฆฌํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ๋” ๋ช…ํ™•ํ•ด์ง€๊ณ , ๊น”๋”ํ•˜๊ณ , ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฌ์šด ๊ตฌ์กฐ๊ฐ€ ๋œ๋‹ค.
๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ๋Š” ๋‹จ์ˆœํ•œ ์„ค๊ณ„ ๊ทœ์น™์ด ์•„๋‹Œ, ์œ ์ง€๋ณด์ˆ˜์„ฑ๊ณผ ํ™•์žฅ์„ฑ์„ ๋†’์ด๋Š” ํ•ต์‹ฌ ์›์น™์ด๋‹ค.

โš ๏ธ ๋‹จ์ 


  1. ๊ณ„์ธต ๊ฐ„ ๊ฒฝ๊ณ„ ์•ฝํ™” ์œ„ํ—˜
    • ํ•œ ๋ ˆ์ด์–ด๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์€ ์ฑ…์ž„์„ ์งˆ ๊ฒฝ์šฐ ๋น„๋Œ€ํ•ด์งˆ ์ˆ˜ ์žˆ๋‹ค. (ํŠนํžˆ Service)
  2. ๋‹จ๋ฐฉํ–ฅ ๊ตฌ์กฐ์˜ ํ•œ๊ณ„
    • ์ง€๋‚˜์น˜๊ฒŒ ๊ณ„์ธตํ™”๋˜๋ฉด ๋ณต์žกํ•œ ๋กœ์ง ํ๋ฆ„์ด ์˜คํžˆ๋ ค ๋ถˆํŽธํ•ด์งˆ ์ˆ˜ ์žˆ๋‹ค.
  3. ๋„๋ฉ”์ธ ์ค‘์‹ฌ์ด ์•„๋‹˜
    • ๋น„์ฆˆ๋‹ˆ์Šค ๋„๋ฉ”์ธ์„ ์ค‘์‹ฌ์œผ๋กœ ์„ค๊ณ„ํ•˜๋Š” DDD, ํด๋ฆฐ ์•„ํ‚คํ…์ณ๋ณด๋‹ค ์œ ์—ฐ์„ฑ์ด ๋‚ฎ๋‹ค.

ยฉ Hoon. Some rights reserved.