[TIL] 백엔드 부트캠프 5주차 (2024/08/14 수) 스케쥴 프로그램 STEP_1 (3 Layer Architecture)

2024. 8. 16. 14:40·TIL 🔖/TIL

💻Today's Schedule

12:00 ~ 18:00 개인학습 (~12 개인일정)
18:00 ~ 19:00 저녁 식사
19:00 ~ 21:00 개인 학습


✍ Today I Learned

CODEKATA

  • 알고리즘 (49번 두 개 뽑아서 더하기)
 

[프로그래머스/JAVA] 두 개 뽑아서 더하기

문제설명정수 배열 numbers가 주어집니다. numbers에서 서로 다른 인덱스에 있는 두 개의 수를 뽑아 더해서 만들 수 있는 모든 수를 배열에 오름차순으로 담아 return 하도록 solution 함수를 완성해주세

fargoewave.tistory.com


스케쥴 앱 만들기 (1단계 구현 / API로직 분리)

공통 조건
1. 일정 작성, 수정, 조회 시 반환 받은 일정 정보에 비밀번호는 제외해야 합니다.
2. 일정 수정, 삭제 시 선택한 일정의 비밀번호와 요청할 때 함께 보낸 비밀번호가 일치할 경우에만 가능합니다.
3. 비밀번호가 일치하지 않을 경우 적절한 오류 코드 및 메세지를 반환해야 합니다.
4. 3 Layer Architecture 에 따라 각 Layer의 목적에 맞게 개발해야 합니다.
5. CRUD 필수 기능은 모두 데이터베이스 연결 및 JDBC 를 사용해서 개발해야 합니다.
7. JDBC 설정은 강의와 강의에서 제공해주는 코드 스니펫을 참조하셔도 됩니다.
8Entity를 그대로 반환하지 말고, DTO에 담아서 반환해주세요

 

1단계 일정작성
1단계 조건
1. 할일, 담당자명, 비밀번호, 작성/수정일을 저장할 수 있습니다.
    a.작성/수정일은 날짜와 시간을 모두 포함한 형태 입니다.
2. 각 일정의 고유 식별자(ID)를 자동으로 생성하여 관리합니다.
3. 최초 입력간에는 수정일은 작성일과 동일합니다.
4. 등록된 일정의 정보를 반환 받아 확인할 수 있습니다.
  • 과제를 진행하며 ERD와 SQL, API 명세까지 설계를 했지만 첫 코드를 진행하는데 어려움을 느껴 제공받은 강의대로 초기 코드 작성을 진행했다.  
  • 강의에서 작성하는 초기 코드는 Controller / Dto / Entity 의 구조를 가지고 있었다. 
    • Presentation Layer: API 요청을 받아서 적절한 서비스를 호출하고, 그 결과를 반환한다. 컨트롤러가 여기에 해당한다.
    • Service Layer: 비즈니스 로직을 처리한다. 요청에 따른 데이터 검증, 비밀번호 확인 등의 로직이 포함된다.
    • Persistence Layer: 데이터베이스와의 직접적인 통신을 담당하며, 엔티티를 저장, 수정, 삭제하는 역할을 한다.
  • DTO (Data Transfer Object) 활용
    • Entity를 그대로 클라이언트에 반환하지 않고, DTO를 사용하여 필요한 데이터만 전달한다. 이를 통해 보안성을 높이고, 필요하지 않은 정보를 클라이언트에 노출하지 않도록 한다. 특히, 비밀번호는 클라이언트에 절대 전달되지 않도록 해야 한다.

 

1. 코드 구현 / Entity 클래스

    • Plans 엔티티는 일정 정보를 관리하는 기본 객체로, 데이터베이스의 테이블과 매핑된다. 작성일(createdAt)과 수정일(updateAt)은 LocalDateTime을 사용해 자동으로 생성된다.
@Getter
@Setter
@NoArgsConstructor
public class Plans {
    private Long id;
    private String todo;
    private String assignee;
    private String password;
    private LocalDateTime createdAt;
    private LocalDateTime updateAt;

    public Plans(PlansRequestDto requestDto) {
        this.todo = requestDto.getTodo();
        this.assignee = requestDto.getAssignee();
        this.password = requestDto.getPassword();
        this.createdAt = LocalDateTime.now();
        this.updateAt = this.createdAt;
    }
}

 

2. 코드 구현 /  DTO 클래스

  • PlansRequestDto는 클라이언트로부터 요청을 받을 때 사용되며, PlansResponseDto는 클라이언트에 응답할 때 사용된다. PlansResponseDto에서는 비밀번호를 제외한 정보만을 포함하도록 설계했다.
@Getter
public class PlansRequestDto {
    private String todo;
    private String assignee;
    private String password;
}

@Getter
public class PlansResponseDto {
    private Long id;
    private String todo;
    private String assignee;
    private LocalDateTime createdAt;
    private LocalDateTime updateAt;

    public PlansResponseDto(Plans plans) {
        this.id = plans.getId();
        this.todo = plans.getTodo();
        this.assignee = plans.getAssignee();
        this.createdAt = plans.getCreatedAt();
        this.updateAt = plans.getUpdateAt();
    }
}

 

3. 코드 구현 /  Controller 클래스

  • PlansController는 클라이언트의 요청을 받아 필요한 서비스를 호출하는 역할을 한다. 여기서는 간단한 일정 생성 기능만을 다루었으며, 이후 확장할 기능에 대해서도 동일한 구조를 따를 수 있다.
  • 이 코드는 POST /api/plans 엔드포인트로 들어온 요청을 처리하여 새로운 일정을 생성한다. 생성된 일정은 PlansResponseDto로 변환되어 반환된다. 이때 비밀번호는 반환되지 않는다.
@RestController
@RequestMapping("/api")
public class PlansController {

    private final Map<Long, Plans> plansList = new HashMap<>();

    @PostMapping("/plans")
    public PlansResponseDto createPlans(@RequestBody PlansRequestDto requestDto) {
        // RequestDto -> Entity
        Plans plans = new Plans(requestDto);

        // Plans 의 max ID 찾기
        Long maxId = plansList.size() > 0 ? Collections.max(plansList.keySet()) + 1 : 1;
        plans.setId(maxId);

        // 데이터 베이스 저장
        plansList.put(plans.getId(), plans);

        // Entity -> ResponseDto
        PlansResponseDto plansResponseDto = new PlansResponseDto(plans);
        return plansResponseDto;
    }
}

 

역할 분리하기

초기 API 로직은 강의를 보며 단계별로 작성했는데, 모든 기능이 컨트롤러 계층에 집중되어 있는 문제가 있다. 컨트롤러가 클라이언트의 요청을 처리하는 동시에 비지니스 로직을 수행하고 DB에 직접 접근하는 등 과도한 책임을 가지고 있어 코드 가독성이 떨어지고 유지보수가 어려워지는 문제가 있었다. 때문에 2단계를 진행하기 전, 요구사항의 3 Layer Architecture를 충족하기 위해 API 로직을 개선하고 확장했다. 

  • 요구사항의 3 Layer Architecture는 애플리케이션을 세 개의 주요 레이어로 분리하는 소프트웨어 아키텍처 패턴이다. 이 패턴은 코드의 응집도와 유지보수성을 높이며, 확장성을 강화하는 데 중점을 둔다. 이번 프로젝트에서 적용한 3 Layer Architecture는 다음과 같이 구성되었다:

1. Presentation Layer (컨트롤러): 컨트롤러는 클라이언트의 요청을 받아 서비스 레이어를 호출하고, 그 결과를 클라이언트에 반환하는 역할을 한다. 이 레이어는 사용자 인터페이스에 해당하며, 주로 HTTP 요청/응답을 처리하는 PlansController가 이 역할을 수행한다. 이를 통해 컨트롤러는 비즈니스 로직이나 데이터베이스 접근에 대한 책임이 제거되어 비교적 깔끔한 코드 구조를 유지할 수 있게 되었다. 

package com.sparta.scheduleapp_pro.controller;

import com.sparta.scheduleapp_pro.dto.request.PlansUpdateRequestDto;
import com.sparta.scheduleapp_pro.service.PlansService;
import com.sparta.scheduleapp_pro.dto.request.PlansRequestDto;
import com.sparta.scheduleapp_pro.dto.response.PlansResponseDto;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class PlansController {

    private final PlansService plansService;

    public PlansController(PlansService plansService) {
        this.plansService = plansService;
    }

    @PostMapping("/plans")
    public PlansResponseDto createPlans(@RequestBody PlansRequestDto requestDto) {
        return plansService.createPlans(requestDto);
    }

    @PutMapping("/plans/{planId}")
    public PlansResponseDto updatePlans (@PathVariable Long planId, @RequestBody PlansUpdateRequestDto requestDto) {
        return plansService.updatePlans(planId, requestDto);
    }
}

 

2. Service Layer (서비스): 서비스 계층은 컨트롤러로부터 전달받은 요청을 처리하며, 핵심 비즈니스 로직을 수행한다 입력된 데이터를 검증하거나, 데이터베이스와 상호작용하여 필요한 로직을 처리한다. 컨트롤러는 서비스 계층과만 상호작용하며, 비즈니스 로직의 변화가 필요할 때도 서비스 계층에서만 수정하면 된다. PlansService 클래스가 이 레이어에서 핵심 역할을 한다.

package com.sparta.scheduleapp_pro.service;

import com.sparta.scheduleapp_pro.dto.request.PlansUpdateRequestDto;
import com.sparta.scheduleapp_pro.repository.PlansRepository;
import com.sparta.scheduleapp_pro.dto.request.PlansRequestDto;
import com.sparta.scheduleapp_pro.dto.response.PlansResponseDto;
import com.sparta.scheduleapp_pro.entity.Plans;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@Service
public class PlansService {

    private final PlansRepository plansRepository;

    public PlansService(PlansRepository plansRepository) {
        this.plansRepository = plansRepository;
    }

    public PlansResponseDto createPlans(PlansRequestDto requestDto) {
        Plans plans = new Plans(requestDto);
        Plans savedPlans = plansRepository.save(plans);
        return new PlansResponseDto(savedPlans);
    }

    public PlansResponseDto updatePlans(Long id, PlansUpdateRequestDto requestDto) {
        Plans existingPlans = plansRepository.findById(id);

        if(existingPlans.getPassword().equals(requestDto.getPassword())) {
            existingPlans.setTodo(requestDto.getTodo());
            existingPlans.setAssignee(requestDto.getAssignee());
            existingPlans.setUpdatedAt(LocalDateTime.now());
            plansRepository.save(existingPlans);
            return new PlansResponseDto(existingPlans);
        } else {
            throw new IllegalArgumentException("Invalid password");
        }
    }

    public PlansResponseDto getPlans(Long id) {
        Plans plans = plansRepository.findById(id);
        return new PlansResponseDto(plans);
    }
}

 

 

3. Data Access Layer (리포지토리): 데이터베이스와의 상호작용을 전담한다. SQL 쿼리나 데이터베이스 트랜잭션 관리 등은 모두 이 계층에서 처리된다. 이로 인해 데이터베이스와의 연동 코드가 서비스 계층에서 분리되어, 보다 명확한 책임 분리가 가능해졌다.

package com.sparta.scheduleapp_pro.repository;

import com.sparta.scheduleapp_pro.entity.Plans;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;

import java.sql.*;
import java.time.LocalDateTime;

@Repository
public class PlansRepository {
    private final JdbcTemplate jdbcTemplate;

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

    public Plans save(Plans plans) {
        final String sql = "INSERT INTO plans (todo, assignee, password, created_at, updated_at) VALUES (?, ?, ?, ?, ?)";
        KeyHolder keyHolder = new GeneratedKeyHolder();

        jdbcTemplate.update(connection -> {
            PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            ps.setString(1, plans.getTodo());
            ps.setString(2, plans.getAssignee());
            ps.setString(3, plans.getPassword());
            ps.setObject(4, LocalDateTime.now());
            ps.setObject(5, LocalDateTime.now());
            return ps;
        }, keyHolder);

        Number key = keyHolder.getKey();
        if (key != null) {
            plans.setId(key.longValue());
        }

        return plans;
    }

    public Plans findById(Long id) {
        String sql = "SELECT * FROM plans WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
            Plans plans = new Plans();
            plans.setId(rs.getLong("id"));
            plans.setTodo(rs.getString("todo"));
            plans.setAssignee(rs.getString("assignee"));
            plans.setPassword(rs.getString("password"));
            plans.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());
            plans.setUpdatedAt(rs.getTimestamp("updated_at").toLocalDateTime());
            return plans;
        }, id);
    }
}

 

포스트맨으로 1단계 결과 확인 


📝 회고

따라가기가 너무 벅차다. 
스트링 강의 정말 재밌게 봤는데, 구글링이나 강의 코드스니팻을 적용하는 게 아니면 시작할 수 조차 없었다.
그래서 0~3단계에서 가장 많은 시간을 소요했다. 

다행히 내배캠에 계신 친절하신 분께서 자바 객체지향과 3Layer Architecture 구조 설계에 대해서
간단하게 설명해주셨는데, 비로소 이 개념들을 이해할 수 있었던 것같다. 
기초가 부족해서 버리는 시간이 많다는 걸 다시 한번 느꼈다. 이번 주말엔 반드시 김영한님을 찾으리라 

하지만 산넘어 산이;;.. 스프링 강의를 한번 더 보고싶은데 시간이 될 지 모르겠다..


🔖Tomorrow's Goal

  • 개인과제
'TIL 🔖/TIL' 카테고리의 다른 글
  • [TIL] 백엔드 부트캠프 5주차 (2024/08/16 금) 스케쥴 프로그램 STEP_ 4~5 (수정, 삭제 기능 추가)
  • [TIL] 백엔드 부트캠프 5주차 (2024/08/15 목) 스케쥴 프로그램 STEP_2~3 (단건, 다건 조회 기능 추가)
  • [TIL] 백엔드 부트캠프 5주차 (2024/08/13 화) 스케쥴 프로그램 STEP_0 (설계단계 / API, ERD, SQL)
  • [TIL] 백엔드 부트캠프 5주차 (2024/08/12 월) 스프링 입문2
fargoe
fargoe
    fargoe
    fargoewave
    fargoe
    GitHub
    전체
    오늘
    어제
    • 분류 전체보기 (166)
      • TIL 🔖 (140)
        • TIL (69)
        • 코딩테스트 (71)
      • DEV (14)
        • Java & Spring (7)
        • MySQL (3)
        • Git&Github (4)
      • 개발지식 (10)
        • 알고리즘 (2)
        • 자료구조 (8)
        • CS (0)
      • 3D (1)
        • Unity (1)
      • ETC (0)
  • 인기 글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
fargoe
[TIL] 백엔드 부트캠프 5주차 (2024/08/14 수) 스케쥴 프로그램 STEP_1 (3 Layer Architecture)
상단으로

티스토리툴바