Post

Controller, Service, Repository, Domain

웹 애플리케이션 계층 구조

image

  • Controller : MVC에서 컨트롤러의 역할을 하고, 클라이언트에서 요청이 들어오면 그 요청을 Service에게 Mapping 해준다.
  • Service : 핵심 비즈니스 로직을 구현한 계층, Repository에 저장된 객체를 이용한다.
  • Domain : DB에 저장할 객체, 보통 Getter와 Setter가 있는 곳이다.
  • Repository : DB에 직접 접근해 도메인 객체를 DB에 저장하고 관리한다. 보통 Interface를 만들고 구현하는 방식으로 사용한다.

클래스 의존 관계

image

  • 보통 Repository의 경우 DB가 바뀌거나 아직 안 정해지는 다양한 경우를 생각해 DB 교체를 쉽게 하기 위해 Interface로 구현한다.

Domain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package hello.hellospring.domain;

import java.util.concurrent.atomic.AtomicLong;

public class MemberDomain {
    private Long id; // 실수에서는 동시성 문제를 막기 위해 AtomicLong을 사용
    private String name;

    public AtomicLong getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 실무에서는 동시성 문제를 해결하기 위해 Thread Safe한 AtomicLong을 사용한다.
  • primitive type인 long이 아니라 Wrapper Class인 Long을 사용한 이유
    • null을 사용할 수 있다
    • primitive type은 기본값이 0으로 설정되기 때문에 id가 없을 때를 나타낼 수가 없다

Repository

Interface

1
2
3
4
5
6
7
8
9
10
11
12
13
package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;

public interface MemberRepository {
     Member save(Member member);
     Optional<Member> findById(Long id);
     Optional<Member> findByName(String name);
     List<Member> findAll();
}

구현체

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
30
31
32
33
34
35
36
37
38
39
package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

public class MemoryMemberRepository implements MemberRepository{
    private Map<Long, Member> store = new ConcurrentHashMap<>();
    private static Long sequence = 0L;

    // 실무에서는 동시성을 고려해 ConcurrentHashMap과 AtomicLong을 사용
    @Override
    public Member save(Member member) {
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }

    @Override
     public Optional<Member> findById(Long id) {
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findByName(String name) {
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
    }

    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }
}
  • 실무에서는 Thread Safe한 ConcurrentHashMap과 AtomicLong을 사용한다
  • Optional : NullPointException(NPE)을 방지할 수 있게 도와주는 클래스. NULL이 반환될 가능성이 있는 메소드에 사용해 필요한 경우에만 사용하는 것이 바람직하다.
This post is licensed under CC BY 4.0 by the author.