2024.12.05 (Spring CRUD)

칙촉·2024년 12월 5일

스프링(Spring)을 활용해 작고 간단한 가상의 일정 관리 앱을 개발했다.
이 프로젝트는 과제와 스프링 학습을 위해 시작했지만, 실제로 CRUD와 RESTful API를 설계하고 구현하는 과정을 통해 많은 것을 배울 수 있었다는 생각이 든다.

🎯 목차

1.CRUD와 API 설계 명세
2.개발 과정


🛠️ CRUD와 API 설계 명세

CRUD란 Create, Read, Update, Delete의 약자로, 데이터를 다루는 가장 기본적인 작업을 뜻한다. 스프링 기반 웹 애플리케이션에서 CRUD는 각 HTTP 메서드와 밀접한 연관이 있다.

기능설명HTTP MethodEndpoint
Create새로운 일정을 추가POST/schedules
Read일정 조회GET/schedules 또는 /schedules/{id}
Update기존 일정 수정PUT 또는 PATCH/schedules/{id}
Delete일정 삭제DELETE/schedules/{id}

✨Create(생성)

  • 새로운 데이터를 추가하는 작업.
  • 보통 HTTP POST 요청으로 처리.
  • 예시
    • Endpoint : POST /schedules
    • Request Body
        {
        "title": "놀기",
        "description": "공부 안하고 엉덩이 벅벅 긁기",
        "userName": "semin"
        "password": "tpals1234"
         }
  • Response : HTTP Status 201 Created
  • Body
      {
      "id": 1,
      "title": "놀기",
      "description": "공부 안하고 엉덩이 벅벅 긁기",
      "userName": "semin"
      "createdAt": "2024-12-06T11:23:58"
      "updatedAt": "2024-12-06T11:23:58"
       }

📖 Read(조회)

  • 데이터를 읽거나 검색하는 작업.
  • HTTP GET 요청으로 처리.
    • 모든 데이터 조회 : 전체 일정 리스트를 가져오는 작업.
    • 단일 데이터 조회 : 특정 ID의 데이터를 가져오는 작업.
  • 예시 :
    • Endpoint : POST /schedules
    • Response : HTTP Status 200 OK
    • Body
       {
        "id": 1,
        "title": "놀기",
        "description": "공부 안하고 엉덩이 벅벅 긁기",
        "userName": "semin"
        "createdAt": "2024-12-06T11:23:58"
        "updatedAt": "2024-12-06T11:23:58"
       }

✏️ Update(수정)

  • 기존 데이터를 수정하는 작업.
  • HTTP PUT 또는 PATCH 요청으로 처리.
    • PUT: 리소스를 전체적으로 대체.
    • PATCH: 리소스의 일부를 수정.
  • 예시:
    • Endpoint : PUT /schedules/{id}
    • Request Body :
      {
        "title": "놀기",
        "description": "공부 안하고 엉덩이 벅벅 긁기",
      }
  • Response : HTTP Status 204 No Content
  • Body
    {
      "id": 1,
      "title": "회의 변경",
      "description": "주간 회의 내용 변경",
      "date": "2024-12-09"
     }

🗑️ Delete(삭제)

  • 데이터를 삭제하는 작업.
  • HTTP DELETE 요청으로 처리.
  • 예시 :
    • Endpoint : DELETE /schedules/{id}
    • Request Body :
      {
        "password": "tpals1234",
      }
  • Response: 204 No Content

⚙️ 개발과정

1 . 요구사항 분석

일정 관리 앱은 간단하지만 명확한 요구사항이 있기에 그에 따르는 것이 중요했다.
주요 요구사항은 다음과 같다.

  • 사용자가 일정을 추가할 수 있어야 한다.
    • 이 때 사용자로부터 받아야 할 정보는 이름, 비밀번호, 제목, 내용이다.
  • 사용자는 전체 일정을 확인하거나 특정 일정을 선택해 세부 정보를 볼 수 있어야 한다.
    • ID를 통한 조회, 날짜 혹은 사용자명에 의한 조회를 할 수 있어야 한다.
  • 이미 저장된 일정은 수정이 가능해야 하며, 이 때 사용자에게 비밀번호를 요구한다.
  • 필요 없는 일정은 삭제할 수 있어야 하며, 마찬가지로 사용자에게 비밀번호를 요구한다.

2. 코드 구조 설계

1. 계층형 아키텍처

프로젝트를 Controller, Service, Repository로 나누고 각각의 책임을 명확히 하기로 했다.

  • Controller : HTTP 요청과 응답 처리.
  • Service : 비즈니스 로직.
  • Repository : 데이터베이스 접근.

이후 DTO를 추가로 설계해 클라이언트와 데이터베이스간의 데이터를 분리하기로 했다.

2. 엔티티 설계

우선 JDBC를 사용해 테이터베이스 테이블과 매칭될 예정인 Schedule 엔티티를 설계했다.

@Getter
@Setter
public class Schedule {

	private Long id;
	private String title;
	private String description;
	private String userName;
	private String password;
	private LocalDateTime postedTime;
	private LocalDateTime updatedTime;
}

3 . DTO설계

일단 기본적으로 클라이언트의 요청과 응답을 처리하기 위해 SchedulerRequestScheduleResponse DTO를 설계했다.

  • ScheduleRequest : 클라이언트가 전달하는 요청 데이터.
  • ScheduleResponse : 클라이언트에게 반환되는 데이터.
@Getter
public class ScheduleRequest {
	private String title;
	private String description;
	private String userName;
	private String password;
}


@Getter
public class ScheduleResponse {
	private Long id;
	private String title;
	private String description;
	private String userName;
	private LocalDateTime createdAt;
	private LocalDateTime updatedAt;
}

여기에 추가적으로 삭제와 수정에 필요한 데이터만을 요청하는 DTO를 추가로 생성했다.

@Getter
public class ScheduleDeleteRequest {
	private String password;
}

@Getter
public class ScheduleUpdateRequest {
	private String title;
	private String description;
	private String password;
}

4. 각 기능 구현

4-1 일정 생성(Create)

  • Controller에서 클라이언트의 요청(JSON 형식)을 받아 Service 계층으로 전달.
  • Service에서 DTO를 엔티티로 변환한 뒤 Repository를 통해 데이터베이스에 저장.
  • 저장된 데이터를 다시 DTO로 변환해 클라이언트에게 반환.
    @Service
    public class ScheduleServiceImpl implements ScheduleService {
    
    	private final ScheduleRepository scheduleRepository;
    
    	public ScheduleServiceImpl(ScheduleRepository scheduleRepository) {
    		this.scheduleRepository = scheduleRepository;
    	}
    
    	@Override
    	public void createSchedule(ScheduleRequest request) {
    		Schedule schedule = new Schedule();
    		schedule.setTitle(request.getTitle());
    		schedule.setDescription(request.getDescription());
    		schedule.setUserName(request.getUserName());
    		schedule.setPassword(request.getPassword());
    		schedule.setPostedTime(LocalDateTime.now());
    		schedule.setUpdatedTime(LocalDateTime.now());
    		scheduleRepository.save(schedule);
    	}
    }

4-2. 일정 조회(Read)

  • 전체 조회와 단일 조회를 구분.
  • 전체 조회는 데이터베이스에서 모든 데이터를 가져와 리스트로 반환.
  • 단일 조회는 ID를 기반으로 특정 데이터를 조회해 반환.
  • 조건 조회는 이름과 날짜를 기반으로 조회한 모든 데이터를 리스트로 반환
    	@Override
    	public List<ScheduleResponse> getAllSchedules() {
    		return scheduleRepository.findAll().stream()
            	.map(this::getScheduleResponse).collect(Collectors.toList());
    	}
    	
    	@Override
    	public ScheduleResponse getScheduleById(Long id) {
    		Schedule schedule = scheduleRepository.findById(id);
    		return getScheduleResponse(schedule);
    	}
    
        private ScheduleResponse getScheduleResponse(Schedule schedule) {
    		ScheduleResponse scheduleResponse = new ScheduleResponse();
    		scheduleResponse.setId(schedule.getId());
    		scheduleResponse.setTitle(schedule.getTitle());
    		scheduleResponse.setDescription(schedule.getDescription());
    		scheduleResponse.setUserName(schedule.getUserName());
    		scheduleResponse.setCreatedAt(schedule.getPostedTime());
    		scheduleResponse.setUpdatedAt(schedule.getUpdatedTime());
    		return scheduleResponse;
    	}
    
        @Override
    	public List<ScheduleResponse> getScheduleByNameAndDate(Optional<String> name, Optional<LocalDate> date) {
    		return scheduleRepository.findByNameAndDate(name,date).stream()
    				.map(this::getScheduleResponse).collect(Collectors.toList());
    	}

4-3 일정 수정(Update)

  • 요청 데이터와 ID를 받아 해당 ID의 데이터를 수정.
  • ID와 데이터(비밀번호)를 조건으로 조회함으로써 따로 비밀번호를 검증하는 로직을 넣지 않음.
  • 데이터가 존재하지 않을 경우 예외를 던져 오류를 명확히 처리.
    	@Override
    	public void updateSchedule(Long id, ScheduleUpdateRequest request) {
    		Optional<Schedule> scheduleOp = scheduleRepository.findByIdAndPassword(id, request.getPassword());
    		Schedule schedule = scheduleOp.orElseThrow(() ->
    				new IllegalArgumentException("can't find schedule or wrong password."));
    
    		schedule.setTitle(request.getTitle());
    		schedule.setDescription(request.getDescription());
    		schedule.setUpdatedTime(LocalDateTime.now());
    		scheduleRepository.update(schedule);
    	}

4-4 일정 삭제(Delete)

  • 요청 데이터와 ID를 받아 해당 ID의 데이터를 삭제.
  • 수정할 때의 경우와 같이 ID와 데이터를 통해 조회하고, 해당 데이터가 존재하지 않을 경우 예외를 던져 오류를 명확히 처리한다.
    	@Override
    	public void deleteSchedule(Long id,ScheduleDeleteRequest request) {
    		Optional<Schedule> scheduleOp = scheduleRepository.findByIdAndPassword(id, request.getPassword());
    		Schedule schedule = scheduleOp.orElseThrow(() ->
    				new IllegalArgumentException("can't find schedule or wrong password."));
    
    		scheduleRepository.deleteById(schedule.getId());
    	}
profile
강세민

0개의 댓글