
전체 패키지 구조는 다음과 같다.

다음 8가지 기능을 구현하기 위해 서비스 코드에 메서드 시그니처들을 정리한다.
package todolist.service;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import todolist.dto.TodoRequest;
import todolist.dto.TodoResponse;
import todolist.entity.TodoEntity;
import todolist.repository.TodoRepository;
@Service
@RequiredArgsConstructor
public class TodoService {
    private final TodoRepository todoRepository;
    @Transactional  // 하나의 작업 단위를 명시하기 위한 어노테이션이라는 점이 default. 하나는 성공하고 하나는 실패하는 경우 이 작업이 실패라는 뜻이다.
    public TodoResponse create(TodoRequest request) {
        TodoEntity instance = new TodoEntity(request.getTitle());
        TodoEntity todoEntity = todoRepository.save(instance);
        return new TodoResponse(todoEntity);
    }
    @Transactional
    public TodoResponse update(Long id, TodoRequest request) {
        // 업데이트 하려는 todo객체 찾기_id 값으로 조회가 되지 않으면 에러를 내보낸다.
        TodoEntity todoEntity = todoRepository.findById(id)
                .orElseThrow(()-> new IllegalArgumentException("잘못된 입력입니다."));
        // 업데이트 한 투두를 담기
        TodoEntity updatedTodo = todoEntity.update(request.getTitle());
        // 응답하려는 객체에 담아서 반환하기.
        return new TodoResponse(updatedTodo);
    }
    @Transactional
    public void delete(Long id) {
        todoRepository.deleteById(id);
    }
    @Transactional
    public TodoResponse get(Long id) {
        // id 값으로 조회가 되지 않으면 에러를 내보낸다.
        TodoEntity todoEntity = todoRepository.findById(id)
                .orElseThrow(()-> new IllegalArgumentException("잘못된 입력입니다."));
        return new TodoResponse(todoEntity);
    }
    @Transactional
    public Page<TodoResponse> getAll(Pageable pageable) {
        // pageable로 todo를 조회
        Page<TodoEntity> todoEntities = todoRepository.findAll(pageable);
        // entity를 그대로 반환하지 않고 한 번 감싼다.
        Page<TodoResponse> todoResponses = todoEntities.map(TodoResponse::new);
        return todoResponses;
    }
}
package todolist.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import todolist.entity.MemberEntity;
import todolist.repository.MemberRepository;
import java.util.List;
import java.util.Optional;
//@Service
public class MemberService {
    private final MemberRepository memberRepository;
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
    // 회원가입
    @Transactional
    public String join(MemberEntity memberEntity) {
        validateDuplicateMember(memberEntity);
        memberRepository.save(memberEntity);
        return memberEntity.getId();
    }
    @Transactional
    private void validateDuplicateMember(MemberEntity memberEntity) {
        memberRepository.findById(memberEntity.getId())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }
    // 회원 조회
    @Transactional
    public Optional<MemberEntity> findOne(String memberId) {
        return memberRepository.findById(memberId);
    }
    // 전체 회원 조회
    @Transactional
    public List<MemberEntity> findMembers() {
        return memberRepository.findAll();
    }
}
package todolist.service;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import todolist.repository.MemberRepository;
import todolist.repository.MemoryMemberRepository;
@Configuration
public class ServiceConfig {
    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}
package todolist.service;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import todolist.entity.MemberEntity;
import todolist.repository.MemberRepository;
import todolist.repository.MemoryMemberRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
class MemberServiceTest {
    MemberService memberService;
    MemberRepository memberRepository;
    @BeforeEach
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }
    @AfterEach
    public void afterEach() {
        memberRepository.clearStore();
    }
    @Test
    void 회원가입() {
        MemberEntity memberEntity = new MemberEntity();
        memberEntity.setId("ohohoh");
        memberEntity.setPw("ohoh123");
        String saveId = memberService.join(memberEntity);
        MemberEntity findMember = memberService.findOne(saveId).get();
        assertThat(memberEntity.getId()).isEqualTo(findMember.getId());
    }
    @Test
    public void 중복_회원_예외() {
        MemberEntity memberEntity1 = new MemberEntity();
        memberEntity1.setId("member1");
        memberEntity1.setPw("member123");
        MemberEntity memberEntity2 = new MemberEntity();
        memberEntity2.setId("member1");
        memberEntity2.setPw("member123");
        // member1 을 가입시키고
        memberService.join(memberEntity1);
        // member2 의 중복 여부를 체크
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(memberEntity2));
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
    }
}