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 7 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
Expand Up @@ -20,6 +20,6 @@ public EventAppResponse saveEvent(EventAppRequest request) {
Event event = request.toEvent(token);
eventRepository.save(event);

return EventAppResponse.of(event);
return EventAppResponse.of(event);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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
memberActions.sort(Comparator.comparing(MemberAction::getSequence));
Arachneee marked this conversation as resolved.
Show resolved Hide resolved
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 validateActions(MemberActionsSaveAppRequest request, List<MemberAction> memberActions) {
for (MemberActionSaveAppRequest action : request.actions()) {
validateAction(memberActions, action);
}
}

private void validateAction(List<MemberAction> memberActions, MemberActionSaveAppRequest action) {
3Juhwan marked this conversation as resolved.
Show resolved Hide resolved
MemberActionStatus memberActionStatus = MemberActionStatus.of(action.status());
if (isInvalidStatus(memberActions, action.name(), memberActionStatus)) {
throw new IllegalArgumentException();
}
}

private boolean isInvalidStatus(List<MemberAction> actions, String name, MemberActionStatus status) {
for (int i = actions.size() - 1; i >= 0; i--) {
MemberAction action = actions.get(i);
if (action.isSameName(name)) {
return action.isSameStatus(status);
kunsanglee marked this conversation as resolved.
Show resolved Hide resolved
3Juhwan marked this conversation as resolved.
Show resolved Hide resolved
}
}
return MemberActionStatus.IN != status;
3Juhwan marked this conversation as resolved.
Show resolved Hide resolved
}
}
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로 받게되면 요청 데이터가 추가되거나 변경될 때 확장성이 떨어진다고 생각하여 한 번 더 감싸서 받기로 결정했었던 것 같아요. 저도 팀원들과 함께 다시 논의해볼 필요는 있다고 생각합니다.

15 changes: 15 additions & 0 deletions server/src/main/java/server/haengdong/domain/Action.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
@Entity
public class Action {

private static final long FIRST_SEQUENCE = 1L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Expand All @@ -23,4 +25,17 @@ public class Action {
private Event event;

private Long sequence;

public Action(Event event, Long sequence) {
this.event = event;
this.sequence = sequence;
}

public static Action createFirst(Event event) {
return new Action(event, FIRST_SEQUENCE);
}

public Action next() {
return new Action(event, sequence + 1);
3Juhwan marked this conversation as resolved.
Show resolved Hide resolved
}
}
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,24 @@
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));
}

public static boolean isMemberStatusIn(MemberActionStatus memberActionStatus) {
return IN == memberActionStatus;
}
3Juhwan marked this conversation as resolved.
Show resolved Hide resolved

public boolean isOpposite(MemberActionStatus memberActionStatus) {
return this != memberActionStatus;
}
3Juhwan marked this conversation as resolved.
Show resolved Hide resolved
}
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
@@ -0,0 +1,22 @@
package server.haengdong.persistence;

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;

@Repository
public interface ActionRepository extends JpaRepository<Action, Long> {

@Query("""
SELECT a
FROM Action a
WHERE a.event = :event
ORDER BY a.sequence DESC
LIMIT 1
""")
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
@@ -1,9 +1,11 @@
package server.haengdong.persistence;

import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import server.haengdong.domain.Event;

@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