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

[BE] 인원 변동 기능 구현 #47

Merged
merged 15 commits into from
Jul 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package server.haengdong.application;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import server.haengdong.application.request.MemberActionSaveAppRequest;
import server.haengdong.application.request.MemberActionsSaveAppRequest;
import server.haengdong.domain.Action;
import server.haengdong.domain.MemberAction;
import server.haengdong.domain.MemberActionStatus;
import server.haengdong.domain.MemberGroupIdProvider;

@RequiredArgsConstructor
@Component
public class MemberActionFactory {

private final MemberGroupIdProvider memberGroupIdProvider;

public List<MemberAction> createMemberActions(
MemberActionsSaveAppRequest request,
List<MemberAction> memberActions,
Action action
) {
3Juhwan marked this conversation as resolved.
Show resolved Hide resolved
validateMemberNames(request);
validateActions(request, memberActions);

Long memberGroupId = memberGroupIdProvider.createGroupId();
List<MemberAction> createdMemberActions = new ArrayList<>();
List<MemberActionSaveAppRequest> actions = request.actions();
for (MemberActionSaveAppRequest appRequest : actions) {
MemberAction memberAction = appRequest.toMemberAction(action, memberGroupId);
createdMemberActions.add(memberAction);
action = action.next();
}

return createdMemberActions;
}

private void validateMemberNames(MemberActionsSaveAppRequest request) {
List<String> memberNames = request.actions().stream()
.map(MemberActionSaveAppRequest::name)
.toList();

long uniqueCount = memberNames.stream().distinct().count();
if (uniqueCount != memberNames.size()) {
throw new IllegalArgumentException();
}
}

private void validateActions(MemberActionsSaveAppRequest request, List<MemberAction> memberActions) {
List<MemberAction> reverseSortedMemberActions = memberActions.stream()
.sorted(Comparator.comparing(MemberAction::getSequence).reversed())
.toList();

for (MemberActionSaveAppRequest action : request.actions()) {
validateAction(action, reverseSortedMemberActions);
}
}

private void validateAction(MemberActionSaveAppRequest request, List<MemberAction> memberActions) {
MemberActionStatus memberActionStatus = MemberActionStatus.of(request.status());
if (isInvalidStatus(memberActions, request.name(), memberActionStatus)) {
throw new IllegalArgumentException();
}
}

private boolean isInvalidStatus(
List<MemberAction> memberActions,
String memberName,
MemberActionStatus memberActionStatus
) {
return memberActions.stream()
.filter(action -> action.isSameName(memberName))
.findFirst()
.map(action -> action.isSameStatus(memberActionStatus))
.orElse(MemberActionStatus.IN != memberActionStatus);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package server.haengdong.application;

import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import server.haengdong.application.request.MemberActionsSaveAppRequest;
import server.haengdong.domain.Action;
import server.haengdong.domain.Event;
import server.haengdong.domain.MemberAction;
import server.haengdong.persistence.ActionRepository;
import server.haengdong.persistence.EventRepository;
import server.haengdong.persistence.MemberActionRepository;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class MemberActionService {

private final MemberActionFactory memberActionFactory;
private final MemberActionRepository memberActionRepository;
private final EventRepository eventRepository;
private final ActionRepository actionRepository;

@Transactional
public void saveMemberAction(String token, MemberActionsSaveAppRequest request) {
Event event = eventRepository.findByToken(token)
.orElseThrow(() -> new IllegalArgumentException("event not found"));

List<MemberAction> findMemberActions = memberActionRepository.findAllByEvent(event);
Action action = createStartAction(event);
List<MemberAction> memberActions = memberActionFactory.createMemberActions(request, findMemberActions, action);
memberActionRepository.saveAll(memberActions);
}

private Action createStartAction(Event event) {
return actionRepository.findLastByEvent(event)
.map(Action::next)
.orElse(Action.createFirst(event));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package server.haengdong.application.request;

import server.haengdong.domain.Action;
import server.haengdong.domain.MemberAction;
import server.haengdong.domain.MemberActionStatus;

public record MemberActionSaveAppRequest(String name, String status) {

public MemberAction toMemberAction(Action action, Long memberGroupId) {
return new MemberAction(action, name, MemberActionStatus.of(status), memberGroupId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package server.haengdong.application.request;

import java.util.List;

public record MemberActionsSaveAppRequest(List<MemberActionSaveAppRequest> actions) {
}
Comment on lines +5 to +6
Copy link
Contributor

Choose a reason for hiding this comment

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

c: DTO에 일급컬렉션을 적용하면 어떤 이점이 있을까요?
presentation 단에서는 주고받는 json형태를 객체로 하기 위해 일급컬렉션을 사용했는데요. application단의 dto까지 일급컬렉션을 적용해야하는지 잘 모르겠습니다. 감싸고 푸는 번거로움만 늘어가는 것 같아요.

Copy link
Contributor

@kunsanglee kunsanglee Jul 20, 2024

Choose a reason for hiding this comment

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

지금의 DTO 구조에서 얻을 수 있는 이점이라고 하면 JSON 요청 본문에 포함되는 데이터가 현재 List 이외에 추가된다면 MemberActionsSaveRequest 객체의 속성으로 쉽게 넣을 수 있다는 점이 있습니다. 저희가 논의했을 때는 요청 본문을 List로 받게되면 요청 데이터가 추가되거나 변경될 때 확장성이 떨어진다고 생각하여 한 번 더 감싸서 받기로 결정했었던 것 같아요. 저도 팀원들과 함께 다시 논의해볼 필요는 있다고 생각합니다.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class EventStep {

@ManyToOne(fetch = FetchType.LAZY)
private Event event;

private String name;

private Long sequence;
Expand Down
22 changes: 21 additions & 1 deletion server/src/main/java/server/haengdong/domain/MemberAction.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package server.haengdong.domain;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
Expand All @@ -21,7 +22,7 @@ public class MemberAction {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToOne(fetch = FetchType.LAZY)
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private Action action;

private String memberName;
Expand All @@ -30,4 +31,23 @@ public class MemberAction {
private MemberActionStatus status;

private Long memberGroupId;

public MemberAction(Action action, String memberName, MemberActionStatus status, Long memberGroupId) {
this.action = action;
this.memberName = memberName;
this.status = status;
this.memberGroupId = memberGroupId;
}

public boolean isSameName(String name) {
return memberName.equals(name);
}

public boolean isSameStatus(MemberActionStatus memberActionStatus) {
return status == memberActionStatus;
}

public Long getSequence() {
return action.getSequence();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
package server.haengdong.domain;

import java.util.Arrays;

public enum MemberActionStatus {
IN,
OUT,
;

public static MemberActionStatus of(String status) {
return Arrays.stream(MemberActionStatus.values())
.filter(s -> s.name().equals(status))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Invalid status: " + status));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package server.haengdong.domain;

import org.springframework.stereotype.Component;

@Component
public class MemberGroupIdProvider {

public Long createGroupId() {
return System.currentTimeMillis();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import server.haengdong.domain.Action;
import server.haengdong.domain.Event;
Expand All @@ -17,5 +18,5 @@ public interface ActionRepository extends JpaRepository<Action, Long> {
ORDER BY a.sequence DESC
LIMIT 1
""")
Optional<Action> findLastByEvent(Event event);
Optional<Action> findLastByEvent(@Param("event") Event event);
3Juhwan marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@

@Repository
public interface EventRepository extends JpaRepository<Event, Long> {

Optional<Event> findByToken(String token);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package server.haengdong.persistence;

import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import server.haengdong.domain.Event;
import server.haengdong.domain.MemberAction;

@Repository
public interface MemberActionRepository extends JpaRepository<MemberAction, Long> {

@Query("select m from MemberAction m join fetch m.action where m.action.event = :event")
List<MemberAction> findAllByEvent(@Param("event") Event event);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package server.haengdong.presentation;

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import server.haengdong.application.MemberActionService;
import server.haengdong.presentation.request.MemberActionsSaveRequest;

@RequiredArgsConstructor
@RestController
public class MemberActionController {

private final MemberActionService memberActionService;

@PostMapping("/api/events/{token}/actions/members")
public ResponseEntity<Void> saveMemberAction(
@PathVariable("token") String token,
@RequestBody MemberActionsSaveRequest request
) {
memberActionService.saveMemberAction(token, request.toAppRequest());

return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package server.haengdong.presentation.request;

import server.haengdong.application.request.MemberActionSaveAppRequest;

public record MemberActionSaveRequest(String name, String status) {

public MemberActionSaveAppRequest toAppRequest() {
return new MemberActionSaveAppRequest(name, status);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package server.haengdong.presentation.request;

import java.util.List;
import server.haengdong.application.request.MemberActionSaveAppRequest;
import server.haengdong.application.request.MemberActionsSaveAppRequest;

public record MemberActionsSaveRequest(List<MemberActionSaveRequest> actions) {

public MemberActionsSaveAppRequest toAppRequest() {
List<MemberActionSaveAppRequest> appRequests = actions.stream()
.map(MemberActionSaveRequest::toAppRequest)
.toList();

return new MemberActionsSaveAppRequest(appRequests);
}
}
Loading
Loading