-
Spring boot todo list 만들기백엔드 2023. 7. 21. 18:28728x90반응형SMALL
이번 포스팅은 스프링부트에 대한 정확한 이해가 동반되지 않은 상태에서 작성된 글입니다. 잘못된 부분에 대해서 댓글 남겨주시면 수정하도록 하겠습니다.
spring boot를 제대로 배워보기 전에 todolist를 먼저 만들어보게 되었습니다.
spring을 이용한 백엔드의 프로젝트 구조에 대해 많이 궁금했었는데 이번을 계기로 어느정도 파악해볼 수 있었습니다.
우선 백엔드의 구조는 model, repository, service, controller 이렇게 4가지 층이 있습니다.
model 실제 데이터베이스에서 사용하는 데이터 Entity입니다. 개발 도메인에 활용되는 각각의 오브젝트입니다. repository 데이터베이스와 데이터를 주고 받기 위한 메서드를 명시한 인터페이스입니다. 데이터를 주고 받는 메서드를 명세함으로써 다른 레이어에서 이 메서드를 통해 데이터에 접근 할 수 있도록 해줍니다. service 실제 데이터 가공이나, 구현해야 할 로직들을 처리하는 단계입니다. controller 서버에서 들어오는 request와 response를 처리하는 단계입니다.
즉, 요청에 대한 응답을 해주는 코드는 컨트롤러에서 구현해줍니다.
컨트롤러 상에서 사용해야 하는 기능들은 서비스 내에서 구현해줍니다.
서비스 코드 상에서 데이터베이스에 대한 요청이 필요할 경우 레포지토리에 정의된 메서드를 이용합니다.
개발 도메인에 필요한 여러 타입의 데이터들에 대해 모델에서 구현해줍니다.
model에 대해서 많은 용어들이 등장했었기 때문에 조금 더 정리해보려고 합니다. (entity, dto, vo, dao)
- entity는 실제 데이터베이스와 1:1 매칭이 된 Class입니다. 테이블 내에 존재하는 Column 만을 멤버변수로 가지게 됩니다.
- dto(data transfer object)는 데이터 전송 객체이므로 주로 데이터를 각 계층 간에 주고 받을 때 사용합니다.
- vo(value object)는 값을 가지고 있는 객체입니다.
- dao(data access object)는 실제 데이터베이스에서 데이터를 조회, 조작하는 객체를 말합니다.
dto와 vo의 차이점은 dto는 계층 간에 주고 받을 때 사용하고, vo의 경우 객체가 값 그 자체를 표현하는 경우가 많아 불변성을 가져 비교 대상이 될 경우 사용하게 된다고 합니다.
여러 자료들을 찾아보니 이 둘을 딱 정해서 사용하는 경우도 있고 혼용하는 경우도 있다고 합니다. 실제 Spring을 통한 개발을 하게 될 경우 더 자세히 알 수 있을 것 같습니다.
todolist 구현
0. 프로젝트 설정
rest api 생성을 위해 spring data rest를 사용했습니다.
데이터베이스 접근을 위해 spring data jpa를 사용했습니다.
간단한 구현을 테스트하는 프로젝트이므로 데이터베이스로는 h2 인메모리 관계형 데이터베이스를 사용했습니다.
클래스에서 많이 사용되는 메서드를 어노테이션으로 사용할 수 있도록 lombok을 사용했습니다.
dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-rest' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'com.h2database:h2' annotationProcessor("org.projectlombok:lombok") compileOnly("org.projectlombok:lombok") }
1. Entity 정의
속성으로는 id, title, order, completed가 있습니다.
기본키는 id로 생성 전략은
GenerationType.IDENTITY
입니다.GenerationType.IDENTITY
: 기본 키 생성을 데이터베이스에 위임 (id가 null일 경우 알아서 AUTO_INCREMENT)@Data @Entity @NoArgsConstructor @AllArgsConstructor public class TodoEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; @Column(nullable = false) private String title; @Column(name="todoOrder", nullable=false) private Long order; @Column(nullable = false) private Boolean completed; }
2. dto 정의
Controller에서 요청 받는 객체와 응답할 객체를 선언해줍니다.
@Data @NoArgsConstructor @AllArgsConstructor public class TodoRequest { private String title; private Long order; private Boolean completed; }
@Data @NoArgsConstructor @AllArgsConstructor public class TodoResponse { private long id; private String title; private Long order; private Boolean completed; private String url; public TodoResponse(TodoEntity todoEntity){ this.id = todoEntity.getId(); this.title = todoEntity.getTitle(); this.order = todoEntity.getOrder(); this.completed = todoEntity.getCompleted(); this.url = "http://localhost:8080/"+this.id; } }
3. Repository 정의
이번에는 jpa를 사용하기 때문에 JpaRepository를 상속받은 인터페이스를 선언해줍니다.
generic 타입에는 TodoEntity와 id type인 Long이 들어갑니다.
@Repository public interface TodoRepository extends JpaRepository<TodoEntity,Long> { }
4. Service 정의
요청을 받을 경우 실제 데이터베이스에 대한 접근과 관련된 로직들을 구현해줍니다.
todolist의 구현 리스트는 다음과 같습니다.
1. 할 일 추가 2. 특정 아이템 조회 3. 전체 목록 조회 4. 특정 아이템 수정 5. 특정 아이템 삭제 6. 전체 목록 삭제
위의 구현리스트에 따라 각각의 메서드를 구현해줍니다.
@Service @AllArgsConstructor public class TodoService { private final TodoRepository todoRepository; //1. 할 일 추가 public TodoEntity add(TodoRequest todoRequest){ TodoEntity todoEntity = new TodoEntity(); todoEntity.setTitle(todoRequest.getTitle()); todoEntity.setOrder(todoRequest.getOrder()); todoEntity.setCompleted(todoRequest.getCompleted()); return this.todoRepository.save(todoEntity); } //2. 특정 아이템 조회 public TodoEntity searchById(Long id){ return this.todoRepository.findById(id) .orElseThrow(()-> new ResponseStatusException(HttpStatus.NOT_FOUND)); } //3. 전체 목록 조회 public List<TodoEntity> searchAll(){ return this.todoRepository.findAll(); } //4. 특정 아이템 수정 public TodoEntity updateById(Long id, TodoRequest request){ TodoEntity todoEntity = this.searchById(id); if(request.getTitle()!=null){ todoEntity.setTitle(request.getTitle()); } if(request.getOrder()!=null){ todoEntity.setOrder(request.getOrder()); } if(request.getCompleted()!=null){ todoEntity.setCompleted(request.getCompleted()); } return this.todoRepository.save(todoEntity); } //5. 특정 아이템 삭제 public void deleteById(Long id){ this.todoRepository.deleteById(id); } //6. 전체 목록 삭제 public void deleteAll(){ this.todoRepository.deleteAll(); } }
5. Controller 정의
위의 6가지 요구사항에 대한 api들을 정의해줍니다.
@CrossOrigin @AllArgsConstructor @RestController @RequestMapping("/") public class TodoController { private final TodoService service; @PostMapping public ResponseEntity<TodoResponse> create(@RequestBody TodoRequest request){ System.out.println("Create"); if(ObjectUtils.isEmpty(request.getTitle())){ return ResponseEntity.badRequest().build(); } if(ObjectUtils.isEmpty(request.getOrder())){ request.setOrder(0L); } if(ObjectUtils.isEmpty(request.getCompleted())){ request.setCompleted(false); } TodoEntity result = this.service.add(request); return ResponseEntity.ok(new TodoResponse(result)); } @GetMapping("{id}") public ResponseEntity<TodoResponse> readOne(@PathVariable Long id){ System.out.println("readOne"); TodoEntity result = this.service.searchById(id); return ResponseEntity.ok(new TodoResponse(result)); } @GetMapping public ResponseEntity<List<TodoResponse>> readAll(){ System.out.println("readAll"); List<TodoEntity> result = this.service.searchAll(); List<TodoResponse> response = result.stream().map(TodoResponse::new) .collect(Collectors.toList()); return ResponseEntity.ok(response); } @PatchMapping("{id}") public ResponseEntity<TodoResponse> update(@PathVariable Long id, @RequestBody TodoRequest request){ System.out.println("update"); TodoEntity result = this.service.updateById(id,request); return ResponseEntity.ok(new TodoResponse(result)); } @DeleteMapping("{id}") public ResponseEntity<?> deleteOne(@PathVariable Long id){ System.out.println("deleteOne"); this.service.deleteById(id); return ResponseEntity.ok().build(); } @DeleteMapping public ResponseEntity<?> deleteAll(){ System.out.println("deleteAll"); this.service.deleteAll(); return ResponseEntity.ok().build(); } }
임시 프로젝트이므로 @CrossOrigin 어노테이션을 통해 모든 도메인에 대한 요청을 허용해주도록 했습니다.
Controller 어노테이션에는
@Controller
와@RestController
두 가지가 있습니다.@Controller
는 view를 반환하기 위해 사용됩니다. 그러므로 data 그 자체를 반환하기 위해서는 각 api마다@ResponseBody
어노테이션을 추가해주어야 합니다.@RestController
와 같은 경우 Rest api 개발을 위해 사용됩니다.@Controller
에@ResponseBody
가 붙어 api 마다 따로@ResponseBody
어노테이션을 추가해줄 필요가 없습니다.@GetMapping
은 =@RequestMapping(method = RequestMethod.GET)
과 동일합니다.각 HTTP 메서드에게도 동일하게 적용됩니다.
@PathVariable
과 같은 경우 메서드 정의 어노테이션에서 정의한 경로 상에서 중괄호 안에 정의된 변수에 대해 사용 할 수 있습니다.@RequestBody
요청 본문을 나타내는 어노테이션 입니다.ResponseEntity는 Http request에 대한 응답 데이터(http 상태 코드, http 헤더, ...)를 포함하는 클래스입니다.
response 안에 데이터가 존재할 경우 ResponseEntity와 이미 정의한 Response 클래스를 통해 반환하게 됩니다.
오늘은 todo list를 통해 Spring의 프로젝트 구조에 대해 배워보았습니다.
api 요청에 대한 흐름은 어느정도 이해했지만 더 많은 프로젝트 구조를 살펴보면서 다양하게 접해봐야 될 것 같습니다.
우선 먼저 사용을 해보면서 많이 알게 되는 타입이기 때문에 우선 구현을 먼저 해보았는데, 앞으로는 Spring의 동작 원리나 특징에 대해서 알아 볼 수 있도록 하겠습니다.
반응형LIST'백엔드' 카테고리의 다른 글
JPA 연관관계 살펴보기 (1:1, 1:N, N:1) (4) 2023.08.21 Entity annotation, Listener (1) 2023.08.19 JPA 쿼리메서드 (0) 2023.08.18 JPA 살펴보기 (0) 2023.08.17 mysql cli 모음 (+ docker) (0) 2022.12.09