Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: 모집공고 등록 API 수정 및 상세 조회 기능 추가 완료 #652

Merged
merged 10 commits into from
Jan 11, 2025
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package page.clab.api.domain.hiring.recruitment.adapter.in.web;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import page.clab.api.domain.hiring.recruitment.application.dto.response.RecruitmentDetailsResponseDto;
import page.clab.api.domain.hiring.recruitment.application.port.in.RetrieveRecruitmentUseCase;
import page.clab.api.global.common.dto.ApiResponse;

@RestController
@RequestMapping("/api/v1/recruitments")
@RequiredArgsConstructor
@Tag(name = "Hiring - Recruitment", description = "모집 공고")
public class RecruitmentDetailsRetrievalController {

private final RetrieveRecruitmentUseCase retrieveRecruitmentUseCase;

@Operation(summary = "모집 공고 상세 조회", description = "ROLE_ANONYMOUS 이상의 권한이 필요함")
@GetMapping("/{recruitmentId}")
public ApiResponse<RecruitmentDetailsResponseDto> retrieveRecruitmentDetails(
@PathVariable(name = "recruitmentId") Long recruitmentId
) {
RecruitmentDetailsResponseDto recruitment = retrieveRecruitmentUseCase.retrieveRecruitmentDetails(recruitmentId);
return ApiResponse.success(recruitment);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recuritment 테이블의 칼럼이 추가되는 변경사항을 확인했어요.
이 변경사항에 대한 SQL문을 PR에 추가해주시면 좋을 것 같아요!

Copy link
Contributor Author

@SongJaeHoonn SongJaeHoonn Jan 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

프로젝트에서 hibernate의 ddl-auto 옵션이 update로 되어있어 칼럼 추가시에는 자동으로 감지되어 쿼리문을 적용해주는 것으로 알고 있습니다.
칼럼의 타입이나 이름, 제약 조건이 변경된 것이 아닌 단순 추가이므로 쿼리를 따로 날리지 않아도 괜찮지 않을까요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다! DB가 컨테이너 상으로 관리되고 있어서 SQL 작성이 필요하다고 생각했었네요!

Copy link
Collaborator

@limehee limehee Jan 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

나중에 작업 내용을 추적하기 위해 PR을 살펴봤을 때, 구조적인 변화를 쉽게 확인할 수 있도록 하기 위함이지 않을까 싶어요.


아래 내용은 해당 작업과는 무관하지만, 생각이 나서 제안드려요.

현재 ddl-auto 설정이 update로 되어 있는데, 일반적으로 프로덕션 환경에서는 none으로 설정하여 사용하는 경우가 많아요. 이는 데이터베이스 변경으로 인한 안정성 문제를 예방하기 위함이에요.

다만, ddl-autonone으로 설정하면 애플리케이션 실행 시 데이터베이스 스키마가 자동으로 변경되지 않기 때문에 변경 사항을 수동으로 관리해야 해요. 그럼에도 이러한 방식은 아래와 같은 장점을 제공해요.

  1. 안정성 강화: 스키마 자동 변경은 실수나 버그로 인해 데이터 손실을 초래할 수 있어요. 프로덕션 환경에서는 이러한 위험을 줄이기 위해 스키마를 명시적으로 관리하는 것이 일반적이에요.
  2. 표준 관리 방식: 많은 프로덕션 환경에서는 FlywayLiquibase와 같은 마이그레이션 도구를 사용하여 스키마 변경을 관리하고, 애플리케이션과 데이터베이스의 상태를 명확히 일치시켜요.
  3. 변경 이력 추적: 수동 관리 방식은 변경 사항의 이력을 명확히 기록하고, 추적할 수 있도록 해요. 이는 문제 발생 시 빠르게 원인을 파악하고 수정하는 데 도움이 될 수 있어요.

저희도 애플리케이션에서 자동 업데이트 대신 수동으로 데이터베이스 스키마를 관리하는 방식을 도입하면 어떨까 싶어요. 이를 통해 변경 사항을 더 명확히 관리하고, 예기치 않은 데이터 손실이나 장애를 예방할 수 있을 것 같아요.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

새로 생긴 칼럼이 NOT NULL이라 기존 데이터를 옮기는걸 생각 못했었는데, SQL문 추가해뒀습니다!

Copy link
Contributor Author

@SongJaeHoonn SongJaeHoonn Jan 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저희도 애플리케이션에서 자동 업데이트 대신 수동으로 데이터베이스 스키마를 관리하는 방식을 도입하면 어떨까 싶어요. 이를 통해 변경 사항을 더 명확히 관리하고, 예기치 않은 데이터 손실이나 장애를 예방할 수 있을 것 같아요.

사실 운영 환경에서 update 사용의 위험성은 알고 있었고, 제가 합류했을 때에도 운영 환경에서 update를 사용함에 의문을 가지고 있었긴 했는데, 로컬 환경에서 update를 계속 사용하다보니 칼럼 추가에 대한 변경은 자동으로 해주는 것이 정말 편했어서 운영 환경에서도 똑같이 적용하면 되어 "편함" 때문에 계속 사용하고 있다고 생각했습니다.

그러나 MAU가 큰 환경에서는 쿼리문 실수 한번으로 피해가 클 수 있기에, none을 사용하는 것이 맞다고 봅니다.
함께 의논 후 적용했으면 합니다!

Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,32 @@ public class RecruitmentJpaEntity extends BaseEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
@Size(min = 1, max = 100, message = "{size.recruitment.title}")
private String title;

@Column(nullable = false)
@Size(min = 1, max = 10000, message = "{size.recruitment.teamIntroduction}")
private String teamIntroduction;

@Column(nullable = false)
private LocalDateTime startDate;

@Column(nullable = false)
private LocalDateTime endDate;

@Column(nullable = false)
@Size(min = 1, max = 10000, message = "{size.recruitment.processTimeline}")
private String processTimeline;

@Column(nullable = false)
@Enumerated(EnumType.STRING)
private ApplicationType applicationType;

@Column(nullable = false)
@Size(min = 1, max = 10000, message = "{size.recruitment.jobDescription}")
private String jobDescription;

@Column(nullable = false)
@Size(min = 1, message = "{size.recruitment.target}")
private String target;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.stereotype.Component;
import page.clab.api.domain.hiring.recruitment.application.dto.request.RecruitmentRequestDto;
import page.clab.api.domain.hiring.recruitment.application.dto.response.RecruitmentDetailsResponseDto;
import page.clab.api.domain.hiring.recruitment.application.dto.response.RecruitmentEndDateResponseDto;
import page.clab.api.domain.hiring.recruitment.application.dto.response.RecruitmentResponseDto;
import page.clab.api.domain.hiring.recruitment.domain.Recruitment;
Expand All @@ -11,9 +12,13 @@ public class RecruitmentDtoMapper {

public Recruitment fromDto(RecruitmentRequestDto requestDto) {
return Recruitment.builder()
.title(requestDto.getTitle())
.teamIntroduction(requestDto.getTeamIntroduction())
.startDate(requestDto.getStartDate())
.endDate(requestDto.getEndDate())
.processTimeline(requestDto.getProcessTimeline())
.applicationType(requestDto.getApplicationType())
.jobDescription(requestDto.getJobDescription())
.target(requestDto.getTarget())
.isDeleted(false)
.build();
Expand All @@ -22,6 +27,7 @@ public Recruitment fromDto(RecruitmentRequestDto requestDto) {
public RecruitmentResponseDto toDto(Recruitment recruitment) {
return RecruitmentResponseDto.builder()
.id(recruitment.getId())
.title(recruitment.getTitle())
.startDate(recruitment.getStartDate())
.endDate(recruitment.getEndDate())
.applicationType(recruitment.getApplicationType())
Expand All @@ -37,4 +43,20 @@ public RecruitmentEndDateResponseDto toEndDateDto(Recruitment recruitment) {
.applicationType(recruitment.getApplicationType())
.build();
}

public RecruitmentDetailsResponseDto toDetailsDto(Recruitment recruitment) {
return RecruitmentDetailsResponseDto.builder()
.id(recruitment.getId())
.title(recruitment.getTitle())
.teamIntroduction(recruitment.getTeamIntroduction())
.startDate(recruitment.getStartDate())
.endDate(recruitment.getEndDate())
.processTimeline(recruitment.getProcessTimeline())
.applicationType(recruitment.getApplicationType())
.jobDescription(recruitment.getJobDescription())
.target(recruitment.getTarget())
.status(recruitment.getStatus().getDescription())
.updatedAt(recruitment.getUpdatedAt())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@
@Setter
public class RecruitmentRequestDto {

@NotNull(message = "{notNull.recruitment.title}")
@Schema(description = "모집 공고 제목", example = "C-Lab Core Team 3기 모집", required = true)
private String title;

@NotNull(message = "{notNull.recruitment.teamIntroduction}")
@Schema(description = "모집 공고 소개", example = "C-Lab Core Team 3기 모집", required = true)
private String teamIntroduction;

@NotNull(message = "{notNull.recruitment.startDate}")
@Schema(description = "모집 시작일", example = "2023-11-06T00:00:00", required = true)
private LocalDateTime startDate;
Expand All @@ -19,10 +27,18 @@ public class RecruitmentRequestDto {
@Schema(description = "모집 종료일", example = "2023-11-08T00:00:00", required = true)
private LocalDateTime endDate;

@NotNull(message = "{notNull.recruitment.processTimeline}")
@Schema(description = "모집 일정", example = "대면 면접 | 09월 10일(화)", required = true)
private String processTimeline;

@NotNull(message = "{notNull.recruitment.applicationType}")
@Schema(description = "구분", example = "CORE_TEAM", required = true)
private ApplicationType applicationType;

@NotNull(message = "{notNull.recruitment.jobDescription}")
@Schema(description = "설명", example = "실무에 가까운 경험을 쌓을 수 있어요.", required = true)
private String jobDescription;

@NotNull(message = "{notNull.recruitment.target}")
@Schema(description = "대상", example = "2~3학년", required = true)
private String target;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,27 @@
@Setter
public class RecruitmentUpdateRequestDto {

@Schema(description = "모집 공고 제목", example = "C-Lab Core Team 3기 모집")
private String title;

@Schema(description = "모집 공고 소개", example = "C-Lab Core Team 3기 모집")
private String teamIntroduction;

@Schema(description = "모집 시작일", example = "2023-11-06T00:00:00")
private LocalDateTime startDate;

@Schema(description = "모집 종료일", example = "2023-11-08T00:00:00")
private LocalDateTime endDate;

@Schema(description = "모집 일정", example = "대면 면접 | 09월 10일(화)")
private String processTimeline;

@Schema(description = "구분", example = "CORE_TEAM")
private ApplicationType applicationType;

@Schema(description = "설명", example = "실무에 가까운 경험을 쌓을 수 있어요.")
private String jobDescription;

@Schema(description = "대상", example = "2~3학년")
private String target;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package page.clab.api.domain.hiring.recruitment.application.dto.response;

import java.time.LocalDateTime;
import lombok.Builder;
import lombok.Getter;
import page.clab.api.domain.hiring.application.domain.ApplicationType;

@Getter
@Builder
public class RecruitmentDetailsResponseDto {

private Long id;
private String title;
private String teamIntroduction;
private LocalDateTime startDate;
private LocalDateTime endDate;
private String processTimeline;
private ApplicationType applicationType;
private String jobDescription;
private String target;
private String status;
private LocalDateTime updatedAt;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
public class RecruitmentResponseDto {

private Long id;
private String title;
private LocalDateTime startDate;
private LocalDateTime endDate;
private ApplicationType applicationType;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package page.clab.api.domain.hiring.recruitment.application.port.in;


import page.clab.api.domain.hiring.recruitment.domain.Recruitment;
import page.clab.api.domain.hiring.recruitment.application.dto.response.RecruitmentDetailsResponseDto;

public interface RetrieveRecruitmentUseCase {

Recruitment getById(Long recruitmentId);
RecruitmentDetailsResponseDto retrieveRecruitmentDetails(Long recruitmentId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import page.clab.api.domain.hiring.recruitment.application.dto.mapper.RecruitmentDtoMapper;
import page.clab.api.domain.hiring.recruitment.application.dto.response.RecruitmentDetailsResponseDto;
import page.clab.api.domain.hiring.recruitment.application.port.in.RetrieveRecruitmentUseCase;
import page.clab.api.domain.hiring.recruitment.application.port.out.RetrieveRecruitmentPort;
import page.clab.api.domain.hiring.recruitment.domain.Recruitment;
Expand All @@ -11,9 +14,12 @@
public class RecruitmentRetrievalService implements RetrieveRecruitmentUseCase {

private final RetrieveRecruitmentPort retrieveRecruitmentPort;
private final RecruitmentDtoMapper mapper;

@Transactional(readOnly = true)
@Override
public Recruitment getById(Long recruitmentId) {
return retrieveRecruitmentPort.getById(recruitmentId);
public RecruitmentDetailsResponseDto retrieveRecruitmentDetails(Long id) {
Recruitment recruitment = retrieveRecruitmentPort.getById(id);
return mapper.toDetailsDto(recruitment);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import page.clab.api.domain.hiring.recruitment.application.dto.request.RecruitmentUpdateRequestDto;
import page.clab.api.domain.hiring.recruitment.application.port.in.RetrieveRecruitmentUseCase;
import page.clab.api.domain.hiring.recruitment.application.port.in.UpdateRecruitmentUseCase;
import page.clab.api.domain.hiring.recruitment.application.port.out.RetrieveRecruitmentPort;
import page.clab.api.domain.hiring.recruitment.application.port.out.UpdateRecruitmentPort;
import page.clab.api.domain.hiring.recruitment.domain.Recruitment;

@Service
@RequiredArgsConstructor
public class RecruitmentUpdateService implements UpdateRecruitmentUseCase {

private final RetrieveRecruitmentUseCase retrieveRecruitmentUseCase;
private final RetrieveRecruitmentPort retrieveRecruitmentPort;
private final UpdateRecruitmentPort updateRecruitmentPort;
private final RecruitmentStatusUpdater recruitmentStatusUpdater;

@Transactional
@Override
public Long updateRecruitment(Long recruitmentId, RecruitmentUpdateRequestDto requestDto) {
Recruitment recruitment = retrieveRecruitmentUseCase.getById(recruitmentId);
Recruitment recruitment = retrieveRecruitmentPort.getById(recruitmentId);
recruitment.update(requestDto);
recruitmentStatusUpdater.updateRecruitmentStatus(recruitment);
recruitment.validateDateRange();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,26 @@
public class Recruitment {

private Long id;
private String title;
private String teamIntroduction;
private LocalDateTime startDate;
private LocalDateTime endDate;
private String processTimeline;
private ApplicationType applicationType;
private String jobDescription;
private String target;
private RecruitmentStatus status;
private Boolean isDeleted;
private LocalDateTime updatedAt;

public void update(RecruitmentUpdateRequestDto recruitmentUpdateRequestDto) {
Optional.ofNullable(recruitmentUpdateRequestDto.getTitle()).ifPresent(this::setTitle);
Optional.ofNullable(recruitmentUpdateRequestDto.getTeamIntroduction()).ifPresent(this::setTeamIntroduction);
Optional.ofNullable(recruitmentUpdateRequestDto.getStartDate()).ifPresent(this::setStartDate);
Optional.ofNullable(recruitmentUpdateRequestDto.getEndDate()).ifPresent(this::setEndDate);
Optional.ofNullable(recruitmentUpdateRequestDto.getProcessTimeline()).ifPresent(this::setProcessTimeline);
Optional.ofNullable(recruitmentUpdateRequestDto.getApplicationType()).ifPresent(this::setApplicationType);
Optional.ofNullable(recruitmentUpdateRequestDto.getJobDescription()).ifPresent(this::setJobDescription);
Optional.ofNullable(recruitmentUpdateRequestDto.getTarget()).ifPresent(this::setTarget);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class SecurityConstants {
public static final String[] PERMIT_ALL_API_ENDPOINTS_GET = {
"/api/v1/applications/{recruitmentId}/{studentId}",
"/api/v1/recruitments", "/api/v1/recruitments/recent-week",
"/api/v1/recruitments/{recruitmentId}",
"/api/v1/news", "/api/v1/news/**",
"/api/v1/blogs", "/api/v1/blogs/**",
"/api/v1/positions", "/api/v1/positions/**",
Expand Down
Loading
Loading