[TIL] 프로젝트 연습, 준비
코드스테이츠 백엔드 부트캠프 D+115
드디어 정규과정이 다 끝이 났다.
4개월이란 짧은 시간에 정말 많은 것을 배웠고
모든 기술을 구현할 수 있는 능력은 없지만
작은 프로젝트부터 시작해 경험을 이제 쌓아올려보려한다.
프로젝트때 내가 중점적으로 적용하고 공부해보려는 것은
- Spring MVC
- Spring AOP
- Spring JPA
- Spring Security(JWT, OAuth2)
- 계층별 테스트코드 작성
- API 문서 자동화
- 배포 자동화
- docker를 이용한 배포환경 통일화
이렇게 8개 정도를 정말 숙달할 정도로 연습해보려한다.
현재 1번~4번까지는 어느정도 연습이 되어있지만
자세한 계층구조를 파악하는 목적을 두고 있다.
5~8번까지는 실제 애플리케이션 동작들을 간편하게 사용하는 목적으로
이용할 가능성이크고 특히 아직 익숙하지 않은 docker에 대해선
팀원들과 상의해 정말 꼭 통일된 환경을 만들어서 작업을 해보고 싶다.
이렇게 프로그램 작성과 적용하여 내부내용까지 정말
이번 기회에 확실히 체득하고 내 것으로 만들 계획이다.
간단한 프로젝트 만들어서 해보기
본격적으로 팀원이 정해지기전에
혼자 어느정도 만들어보는 시간을 가지는 시간이 주어졌다.
오늘은 Todo List를 구현하는 프로그램을 만들어 보려한다.
클라이언트는 이미 오픈으로 제공되어 있는 Todo-Backend사이트를 이용해
서버와 H2 데이터베이스를 이용해 작성해볼 생각이다.
로컬 환경에서 테스트를 하고자한다면
https://todobackend.com/client/index.html?http://localhost:8080
해당 URL로 접속 시 TODO LIST에 대한 GET 요청이 이루어질 것이다.
GET,POST,PATCH,DELETE 까지 적용해보자
우선 먼저 내가 사용할 기술들에 대한
의존 라이브러리를 추가해주었다.
내장 톰켓 서버를 사용할 것이고, H2를 사용할 거다.
그리고 Mapper를 이용할 것이고, DB에 접근하는 기술은 JPA를 사용할 것이다.
그리고 편의를 위한 롬복과, 테스트 코드 작성을 위한 의존라이브러리
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
implementation 'com.google.code.gson:gson'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
이 정도를 추가해볼 수 있을 것 같다.
이제 의존라이브러리 설정을 다해주었으니
웹 애플리케이션 계층 구조를 Controller, Service, Repository로 만들어
적절한 구현과 역할을 나눠주자.
우선 Controller 부터 작성 했다.
@Slf4j\
@RestController
@AllArgsConstructor
public class TodoListController {
private final TodoListService todoListService;
private final TodoListMapper todoListMapper;
@PostMapping
public ResponseEntity createList(@Valid @RequestBody TodoListDto todoListDto){
TodoList todoList = todoListService.createTodoList(todoListMapper.todoListDtoToTodoList(todoListDto));
TodoListResponseDto todoListResponseDto = todoListMapper.todoListToTodoListResponseDto(todoList);
return new ResponseEntity(todoListResponseDto, HttpStatus.CREATED);
}
@GetMapping
public ResponseEntity getLists(){
List<TodoList> todoLists = todoListService.getLists();
return new ResponseEntity(todoLists,HttpStatus.OK);
}
@GetMapping("/{listId}")
public ResponseEntity getList(@PathVariable Long listId){
TodoList todoList = todoListService.getList(listId);
TodoListResponseDto todoListResponseDto = todoListMapper.todoListToTodoListResponseDto(todoList);
return new ResponseEntity(todoListResponseDto,HttpStatus.OK);
}
@PatchMapping("/{listId}")
public ResponseEntity updateList(@PathVariable Long listId,
@RequestBody TodoListDto todoListDto){
TodoList todoList = todoListService.changeTodoList(listId, todoListMapper.todoListDtoToTodoList(todoListDto));
TodoListResponseDto todoListResponseDto = todoListMapper.todoListToTodoListResponseDto(todoList);
return new ResponseEntity(todoListResponseDto,HttpStatus.OK);
}
@DeleteMapping
public ResponseEntity deleteLists(){
todoListService.deleteTodoLists();
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
@DeleteMapping("/{listId}")
public ResponseEntity deleteList(@PathVariable Long listId){
todoListService.deleteTodoList(listId);
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
}
크게 어려운 기술들은 없다.
총 6개의 API로 가장 간단한 동작들만하는 메서드이다.
Entity 클래스 작성이다.
JPA기술을 사용하기 때문에 해당 관련 설정을 해주어야한다.
@Entity
@Getter
@Setter
@NoArgsConstructor
public class TodoList {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private Long todoOrder;
private boolean completed;
public boolean getCompleted(){
return this.completed;
}
}
4개의 필드변수가 존재하고
테이블을 4개만 사용할 예정이다.
Repository 계층이다.
public interface TodoListRepository extends JpaRepository<TodoList,Long> {
}
JpaRepository를 상속받고, 우리가 Entity클래스로 지정한
TodoList와 Id 타입인 Long을 타입매개변수로 넣어주자
이제 Controller에서 주고받는 데이터를
맵핑해주는 Dto 클래스를 만들어보자
@Getter
@Setter
public class TodoListDto {
@NotBlank(message = "제목은 공백이 아니어야 합니다.")
private String title;
private Long order;
private boolean completed;
public boolean getCompleted(){
return completed;
}
}
@Getter
@Setter
public class TodoListResponseDto {
private Long id;
private String title;
private Long todoOrder;
private boolean completed;
}
첫번째 Dto는 Json객체로 요청이 왔을때
맵핑을하도록 작성해주었다.
아직 Valid 대한 예외처리는 적용하지 못한 상태이다.
두번째 Dto는 최종적으로 Reponse의 Body로 응답해줄
Json객체를 만들기위해 맵핑해주는 Dto객체이다.
그리고 Mapper 클래스를 만들어보자
@Mapper(componentModel = "spring")
public interface TodoListMapper {
default TodoList todoListDtoToTodoList(TodoListDto todoListDto){
if ( todoListDto == null ) {
return null;
}
TodoList todoList = new TodoList();
todoList.setTitle( todoListDto.getTitle() );
todoList.setTodoOrder(todoListDto.getOrder());
todoList.setCompleted( todoListDto.getCompleted());
return todoList;
}
TodoListResponseDto todoListToTodoListResponseDto(TodoList todoList);
}
Mapper를 통해서 TodoListDto객체를 TodoList 클래스로
TodoList객체를 TodoListResponseDto클래스로 변경해준다.
이제 간단한 서비스로직에 대한 예외처리를 위한
클래스를 만들어 보자
public enum ExceptionCode {
LIST_NOT_FOUND(404,"TodoList Not Found");
@Getter
private int status;
@Getter
private String message;
ExceptionCode(int code, String message){
this.status = code;
this.message = message;
}
}
public class ServiceLogicException extends RuntimeException{
@Getter
private ExceptionCode exceptionCode;
public ServiceLogicException(ExceptionCode exceptionCode){
super(exceptionCode.getMessage());
this.exceptionCode = exceptionCode;
}
}
이제 해당 커스텀 코드로 예외를 발생시킬 것이다.
리스트의 존재여부와 같은 예외가 발생하면
해당 예외를 발생시켜 추후에 Advice로 잡아서 처리해줄 수 있다.
이제 마지막으로 Service 로직을 작성해보자
@Service
@Transactional
@AllArgsConstructor
public class TodoListService {
private final TodoListRepository todoListRepository;
public TodoList createTodoList(TodoList todoList){
return saveTodoList(todoList);
}
public TodoList getList(Long listId){
return findTodoList(listId);
}
public List<TodoList> getLists(){
return todoListRepository.findAll();
}
public TodoList changeTodoList(Long listId, TodoList todoList){
TodoList findTodoList = findTodoList(listId);
Optional.ofNullable(todoList.getTitle()).ifPresent(title -> findTodoList.setTitle(title));
Optional.ofNullable(todoList.getTodoOrder()).ifPresent(todoOrder -> findTodoList.setTodoOrder(todoOrder));
Optional.ofNullable(todoList.getCompleted()).ifPresent(completed -> findTodoList.setCompleted(completed));
return saveTodoList(findTodoList);
}
public void deleteTodoLists(){
todoListRepository.deleteAll();
}
public void deleteTodoList(Long listId){
findTodoList(listId);
todoListRepository.deleteById(listId);
}
private TodoList findTodoList(Long listId) {
Optional<TodoList> findTodoList = todoListRepository.findById(listId);
return findTodoList.orElseThrow(() -> new ServiceLogicException(ExceptionCode.LIST_NOT_FOUND));
}
private TodoList saveTodoList(TodoList todoList) {
return todoListRepository.save(todoList);
}
}
아주 기본적인 서비스로직을 구현했다.
트랜잭션 전파관련해 건드릴 만한 서비스도 존재하지 않아
클래스단에 @Transactional을 붙여주었다.
JPA 기술을 이용해 TodoList객체만 보내주면
DB에 맵핑이 되어진다.
이렇게 간단하게 API요청과 응답에 대한 내용은
전부 처리가 완료되었다.
이제부터 좀더 다듬어야할 부분은
- 유효성 검증에 처리 추가
- 유효성 검증 실패에 따른 Advice 클래스 작성 필요
- 계층별 테스트 케이스 만들기
- API 문서 자동화 해보기
이렇게 코드를 추가해 볼 수 있을 것 같다.
그리고 현재 로컬서버에서만 작동할 수 있게 되어있는데
Ngrok를 이용해 로컬 환경에 접근할 수 있게 터널링 해주는 프로그램도
이용해 볼 수 있을 것 같다.
우선적으로 터널링을 이용해 테스트를 해본다음
위에 4가지 기능들을 구현할 시간이 주어진다면 내일 이어서 작업해볼 예정이다.
오늘은 이렇게 간단하게 웹계층을 만들어보는 시간을 가졌다.
Spring MVC관련 계층 프로그램은 이제
다른 프로그램을 보지않고도 할 수 있는 정도가 되었다.
하지만 예외를 처리하는 Advice 클래스 구현이라든가
테스트 관련 계층은 문법이 생각나지않아 한번씩 보면서 작성을 해야한다…
오늘 공부는 여기서 끝 !!
오늘의 커피량: ☕️ ☕️
오늘의 점심: 제육볶음, 스팸부침, 밥