Spring

Spring을 3 Layer Architecture로 역할 분리하기

waVwe 2024. 8. 6. 14:37
728x90
반응형

👇🏻 이전 글

2024.08.05 - [Spring] - Spring과 MySQL로 CRUD 기능이 있는 메모장 만들기

 

Spring 프로젝트를 3 Layer Architecture로 역할 분리하기

 

3 Layer Architecture 란 처리 과정을 크게 Controller, Service, Repository 로 나누는 것을 뜻함.

  • Controller : 클라이언트에서 온 요청을 받아 Service로 넘겨주고 처리 완료된 결과를 클라이언트에게 보여주는 역할
  • Service : 사용자의 요구 사항을 처리. 비즈니스 로직 구현하는 곳. DB 저장 및 조회가 필요한 경우 Repository에 요청.
  • Repository : DB 관리. CRUD 작업 처리.

 


 

Controller에서 Service 분리하기

 

Controller는 클라이언트에게서 받은 요청을 그에 알맞는 Service로 넘겨주는 역할이므로 Controller에서 구현된 코드 대부분을 Serviec로 옮기게 된다.

 

MemoService.java

public class MemoService {

    private final JdbcTemplate jdbcTemplate;

    public MemoService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public MemoResponseDto createMemo(MemoRequestDto requestDto) {
        // RequestDto -> Entity
        Memo memo = new Memo(requestDto);
		
        // DB 저장
        KeyHolder keyHolder = new GeneratedKeyHolder(); // 기본 키를 반환받기 위한 객체

        String sql = "INSERT INTO memo (username, contents) VALUES (?, ?)";
        jdbcTemplate.update( con -> {
                    PreparedStatement preparedStatement = con.prepareStatement(sql,
                            Statement.RETURN_GENERATED_KEYS);

                    preparedStatement.setString(1, memo.getUsername());
                    preparedStatement.setString(2, memo.getContents());
                    return preparedStatement;
                },
                keyHolder);

        // DB Insert 후 받아온 기본키 확인
        Long id = keyHolder.getKey().longValue();
        memo.setId(id);

        // Entity -> ResponseDto
        MemoResponseDto memoResponseDto = new MemoResponseDto(memo);

        return memoResponseDto;
    }
}

 

  1. 먼저 Service 패키지를 만들고 안에 MemoService 클래스를 만든다.
  2. MemoService에서도 JDBCtemplate를 사용해야 하기 때문에 Controller와 동일하게 JDBCtemplate를 받는 생성자를 만들어준다.
  3. Service안에 구현될 메소드는 Controller와 동일한 이름과 파라미터를 받는 createMemo로 만든다.
  4. Controller의 createMemo의 반환 타입이 MemoResponseDto 였기 때문에 Service의 메소드 반환 타입 또한 동일하게 해준다.

 

MemoController.java

public class MemoController {

    private final JdbcTemplate jdbcTemplate;

    public MemoController(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @PostMapping("/memos")
    public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
        MemoService memoService = new MemoService(jdbcTemplate);
        return memoService.createMemo(requestDto);
    }
}

 

위와 같은 방법으로 Controller에서 Service를 분리해주면 Controller에는 간결한 코드만이 남게 된다.

 


 

Service에서 Repository 분리하기

 

DB 연결 및 CRUD 작업은 Repository에서 이루어져야 하므로 Service에서 Repository 부분을 분리한다.

 

MemoRepository.java

public class MemoRepository {

    private final JdbcTemplate jdbcTemplate;

    public MemoRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public Memo save(Memo memo) {
        KeyHolder keyHolder = new GeneratedKeyHolder(); // 기본 키를 반환받기 위한 객체

        String sql = "INSERT INTO memo (username, contents) VALUES (?, ?)";
        jdbcTemplate.update( con -> {
                    PreparedStatement preparedStatement = con.prepareStatement(sql,
                            Statement.RETURN_GENERATED_KEYS);

                    preparedStatement.setString(1, memo.getUsername());
                    preparedStatement.setString(2, memo.getContents());
                    return preparedStatement;
                },
                keyHolder);

        // DB Insert 후 받아온 기본키 확인
        Long id = keyHolder.getKey().longValue();
        memo.setId(id);

        return memo;
    }
}

 

  1. Repository 패키지를 만든 후 안에 MemoRepository 클래스를 만든다.
  2. DB와 연결되는 곳이기 때문에 JDBCtemplate를 사용할 수 있도록 생성자를 구현한다.
  3. Memo 객체를 파라미터로 받는 save라는 이름의 메소드를 선언한다.
  4. MemoService에서는 Controller에서 받아온 requestDto를 자바 객체로 변환해주는 작업을 마친 뒤 MemoRepository로 넘겨주게 된다.
  5. MemoRepository는 넘겨받은 객체로 나머지 DB 작업을 하게 된다.

 

MemoService.java

public class MemoService {

    private final JdbcTemplate jdbcTemplate;

    public MemoService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public MemoResponseDto createMemo(MemoRequestDto requestDto) {
        // RequestDto -> Entity
        Memo memo = new Memo(requestDto);

        // DB 저장
        MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
        Memo saveMemo = memoRepository.save(memo);

        // Entity -> ResponseDto
        MemoResponseDto memoResponseDto = new MemoResponseDto(memo);

        return memoResponseDto;
    }
}

 

MemoService는 반환 값으로 MemoResponseDto를 뱉어야 하기 때문에 Repository를 통해 DB에 저장하고 나면 사용한 memo 객체를 responseDto로 변환해서 반환한다.

 


 

조회하기, 수정하기, 삭제하기 기능 또한 위와 같은 방법으로 3계층에 나눠 분리 할 수 있다.

다만 수정하기와 삭제하기에서 사용하는 메소드 findById( )는 DB 조회 메소드이기 때문에 Repository에 적어주게 되는데 이를 Service에서 사용하기 위해 접근제어자를 private → public으로 변경해줘야 한다.

 

수정하기 기능 예시 >

public Long updateMemo(Long id, MemoRequestDto requestDto) {

        MemoRepository memoRepository = new MemoRepository(jdbcTemplate);

        // 해당 메모가 DB에 존재하는지 확인
        Memo memo = memoRepository.findById(id);

        if(memo != null) {
            // memo 내용 수정
            memoRepository.update(id, requestDto);

            return id;
        } else {
            throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
        }
    }

 


 

객체 중복 생성 문제 해결

 

Controller와 Service 코드를 보면 JDBCtemplate를 넘겨주는 코드 한 줄이 메소드 마다 반복되어 적혀 있는 것을 확인 할 수 있다. 이는 생성자 주입으로 다음과 같이 해결 할 수 있다.

 

MemoController.java

    private final MemoService memoService;

    public MemoController(JdbcTemplate jdbcTemplate) {
        this.memoService = new MemoService(jdbcTemplate);
    }

 

위와 같이 적으면 MemoController를 생성할 때 생성자를 통해 자동으로 MemoService에 JDBCtemplate을 넘겨주게 되고 MemoService 선언도 한번만 하면 된다.

같은 방법으로 Service에서 Repository로 JDBCtemplate 넘겨주는 부분도 변경해준다.

 


 

TIL 💭

더보기

이렇게 역할이나 용도에 따라 코드를 나누는 것은 프로젝트를 한 눈에 파악하기도 쉽고 코드를 작성하거나 수정 할 때 등 모든 면에서 편리한 것 같다.

728x90
반응형