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

3주차 과제 (윤석호) #17

Open
wants to merge 9 commits into
base: YoonSeokHo3
Choose a base branch
from
Open
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
19 changes: 19 additions & 0 deletions src/main/java/com/econovation/third_project/config/Field.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.econovation.third_project.config;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public enum Field {
WEB("WEB"),
APP("APP"),
AI("AI"),
GAME("GAME"),
IOT("IoT"),
ARVR("AR/VR");

private final String fieldName;


}
14 changes: 14 additions & 0 deletions src/main/java/com/econovation/third_project/config/Job.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.econovation.third_project.config;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public enum Job {
DEVELOPER("개발자"),
PLANNER("기획자"),
DESIGNER("디자이너");

private final String jobName;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.econovation.third_project.config;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public enum SupportPath {
POSTER("홍보 포스터"),
DEPARTMENT_ANNOUNCEMENT("학과 공지사항"),
ACQUAINTANCE("지인 소개"),
INSTAGRAM("인스타그램"),
EVERYTIME("에브리타임");
private final String entryPathName;

}
Original file line number Diff line number Diff line change
@@ -1,28 +1,105 @@
package com.econovation.third_project.controller;

import com.econovation.third_project.database.Database;
import com.econovation.third_project.database.Registration;
import com.econovation.third_project.service.DesiredTimeService;
import com.econovation.third_project.service.PathService;
import com.econovation.third_project.service.PersonalInformationService;
import com.econovation.third_project.service.RegistrationService;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.function.Supplier;
import static java.util.stream.Collectors.*;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequiredArgsConstructor
@RequestMapping("/api/admin")
public class AdminQueryController {
private final Database database;
private final RegistrationService registrationService;
private final PathService pathService;
private final PersonalInformationService personalInformationService;
private final DesiredTimeService desiredTimeService;

// 예시 코드
@PostMapping("/registration")
public ResponseEntity<Object> postRegistrate(@RequestBody Registration registration) {
database.register(registration);
return ResponseEntity.ok().build();

//모든 데이터 한 번에 반환
@GetMapping("/applicants")
public ResponseEntity<Map<String, List<?>>> getApplicantsInfo(){
return ResponseEntity.ok().body(Map.of(
"job", registrationService.getApplicantNumbersEachJob(),
"field", registrationService.getApplicantNumberEachField(),
"major", personalInformationService.getApplicantNumberEachMajor(),
"path", pathService.getApplicantNumberEachPath(),
"desired_time", desiredTimeService.getApplicantNumberEachTime()
));
Copy link
Collaborator

Choose a reason for hiding this comment

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

컨트롤러에서 집계 호출 때리지 마세요. 컨트롤러는 위 서비스의 기술 스펙이다 생각하셔야지 구현체가 아닙니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

아 그렇군요 어떻게 할지 고민해보겠습니다

}
@GetMapping("/registration")
public ResponseEntity<Registration> getRegistration(String userId) {
return ResponseEntity.ok().body(database.getRegistration(userId));

//동적 응답 + 병렬 스트림
@GetMapping("/applicants/parallel")
public ResponseEntity<Map<String, List<?>>> getApplicantsInfo(@RequestBody List<String> fields){
Map<String, Supplier<List<?>>> services = Map.of(
"job", registrationService::getApplicantNumbersEachJob,
"field", registrationService::getApplicantNumberEachField,
"major", personalInformationService::getApplicantNumberEachMajor,
"path", pathService::getApplicantNumberEachPath,
"desired_time", desiredTimeService::getApplicantNumberEachTime
);
Map<String, List<?>> result = fields.stream().parallel()
.filter(services::containsKey)
.collect(toMap(
Function.identity(),
requestField-> services.get(requestField).get()
)
);

return ResponseEntity.ok().body(result);
}

//동적 응답 + async
@GetMapping("/applicants/async")
public ResponseEntity<Map<String, List<?>>> getApplicantsInfoAsync(@RequestBody List<String> fields){
Map<String, Supplier<List<?>>> services = Map.of(
"job", registrationService::getApplicantNumbersEachJob,
"field", registrationService::getApplicantNumberEachField,
"major", personalInformationService::getApplicantNumberEachMajor,
"path", pathService::getApplicantNumberEachPath,
"desired_time", desiredTimeService::getApplicantNumberEachTime
);

Map<String, CompletableFuture<List<?>>> futures = fields.stream()
.collect(toMap(
Function.identity(),
field -> {
Supplier<List<?>> service = services.get(field);
if (service == null)
throw new IllegalArgumentException();
return CompletableFuture.supplyAsync(service);
}
));

CompletableFuture<Map<String, List<?>>> result = CompletableFuture.allOf(
futures.values().toArray(CompletableFuture[]::new)
).thenApply(v ->
fields.stream()
.collect(toMap(Function.identity(), field-> futures.get(field).join()))
);

return ResponseEntity.ok().body(result.join());
}


//TODO:
// 병렬 스트림 + async의 장점,
// 병렬 스트림, async 각각 적절한 상황,
// 서비스 메서드마다 다른 인자가 필요한 경우,
// 일부 의존 관계가 있을경우 어떻게 처리할지
// 서비스 메서드 맵 어떻게 뺄지,
// 적절한 스레드 수 알아보기,
// 자바 비동기 기초,

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.econovation.third_project.controller;

import com.econovation.third_project.dto.CreateApplicationReq;
import com.econovation.third_project.service.ApplicationService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/api")
@RequiredArgsConstructor
@RestController
public class ApplicationController {
private final ApplicationService applicationService;

@PostMapping("/application")
public ResponseEntity<Integer> createApplication(@RequestBody CreateApplicationReq createApplicationReq){
return ResponseEntity.ok().body(applicationService.createApplication(createApplicationReq));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.econovation.third_project.database;

import java.util.concurrent.atomic.AtomicInteger;
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;


@Getter
public final class Application {
private static final AtomicInteger lastUsedId = new AtomicInteger(0);
@NonNull
private final Integer applicationId = lastUsedId.getAndIncrement();
@NonNull
private final Registration registration;
@NonNull
private final PersonalInformation personalInformation;
@NonNull
private final Path path;
@NonNull
private final DesiredTime desiredTime;

@Builder
public Application(Registration registration, PersonalInformation personalInformation,
Path path,
DesiredTime desiredTime) {
this.registration = registration;
this.personalInformation = personalInformation;
this.path = path;
this.desiredTime = desiredTime;
}
}




72 changes: 60 additions & 12 deletions src/main/java/com/econovation/third_project/database/Database.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.econovation.third_project.database;

import java.util.HashMap;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.stereotype.Component;

/**
Expand All @@ -11,18 +12,65 @@
*/
@Component
public class Database {
private final Map<String, Registration> registration = new HashMap<>();
private final Map<String, Path> path = new HashMap<>();
private final Map<String, PersonalInformation> personalInformation = new HashMap<>();
private final Map<String, DesiredTime> desiredTime = new HashMap<>();
//key: user id, value: application id
private final Map<Integer, Integer> applications = new ConcurrentHashMap<>();
//key: application id
private final Map<Integer, Registration> registrations = new ConcurrentHashMap<>();
//key: application id
private final Map<Integer, Path> paths = new ConcurrentHashMap<>();
//key: application id
private final Map<Integer, PersonalInformation> personalInformation = new ConcurrentHashMap<>();
//key: application id
private final Map<Integer, DesiredTime> desiredTimes = new ConcurrentHashMap<>();


public void register(Registration registrationRequest) {
// 기본적으로 이전 제출시 등록된 정보가 있으면 덮어쓰기를 지원합니다.
registration.put(UUID.randomUUID().toString(), registrationRequest);
public Collection<Registration> getAllRegistrations(){
return registrations.values();
}
public Collection<PersonalInformation> getAllPersonalInformation(){
return personalInformation.values();
}
public Collection<Path> getAllPath() {
return paths.values();
}
public Collection<DesiredTime> getAllDesiredTime(){
return desiredTimes.values();
}

public Registration getRegistration(String userId) {
return registration.get(userId);
//지원자는 userId가 없으니 같은 지원서인지 판단할 기준을 따로 만들어야 함
public Integer upsertApplication(Integer userId, Application application){
Integer applicationId = applications.computeIfAbsent(userId, k -> application.getApplicationId());
upsertRegistration(applicationId, application.getRegistration());
upsertPath(applicationId, application.getPath());
upsertDesiredTime(applicationId, application.getDesiredTime());
upsertPersonalInformation(applicationId, application.getPersonalInformation());
return applicationId;
}
public void upsertRegistration(Integer applicationId, Registration registration){
registrations.put(applicationId, registration);
}
public void upsertPath(Integer applicationId, Path path){
paths.put(applicationId, path);
}
public void upsertPersonalInformation(Integer applicationId, PersonalInformation personalInformation){
this.personalInformation.put(applicationId, personalInformation);
}
public void upsertDesiredTime(Integer applicationId, DesiredTime desiredTime){
desiredTimes.put(applicationId, desiredTime);
Comment on lines +40 to +58
Copy link
Collaborator

Choose a reason for hiding this comment

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

upsert라는 개념을 put 한줄로 표현한 부분 아주 깔끔합니다. 아주 잘하셨습니다.

차 후, Map이 아니라, 일반 클래스에서도 적용하는 부분도 한번 짜보세요!

Copy link

Choose a reason for hiding this comment

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

hashMap에 key가 중복인 데이터를 넣을 때 value가 덮어씌워진다는 것을 이용하여 upsert로 표현한게 멋지네요

}



// public void register(Registration registrationRequest) {
// // 기본적으로 이전 제출시 등록된 정보가 있으면 덮어쓰기를 지원합니다.
// registration.put(UUID.randomUUID().toString(), registrationRequest);
// }
//
// public Registration getRegistration(String userId) {
// return registration.get(userId);
// }





}
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
package com.econovation.third_project.database;

import java.util.HashMap;
import java.util.List;
import lombok.AllArgsConstructor;
import java.util.Map;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class DesiredTime {
String registrationId;
// 희망 시간 (11 * 3)의 테이블 형태를 준수합니다.
private List<int[]> desiredTime;
private static final Map<List<Integer>, List<Integer>> cachedTimes = new HashMap<>();
private final List<List<Integer>> desiredTimes;

public DesiredTime(List<List<Integer>> desiredTimes) {
validateDesiredTime(desiredTimes);
this.desiredTimes = desiredTimes.stream()
.map(time-> cachedTimes.computeIfAbsent(time, t->t))
.toList();
}
private void validateDesiredTime(List<List<Integer>> desiredTimes) {
//27기 기준
int interviewDays = 3;
int hoursPerDay = 11;

desiredTimes.forEach(
time -> {
if (time.get(0) >= interviewDays || time.get(1) >= hoursPerDay)
throw new IllegalArgumentException("면접 시간 잘못 입력");
}
);

}
}
23 changes: 19 additions & 4 deletions src/main/java/com/econovation/third_project/database/Path.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
package com.econovation.third_project.database;

import com.econovation.third_project.config.SupportPath;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collector;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class Path {
String registrationId;
public class Path{
// 지원 경로
private String supportPath;
private final EnumSet<SupportPath> supportPaths;

public Path(EnumSet<SupportPath> supportPaths) {
this.supportPaths = supportPaths;
}

public static Path from(Set<String> stringPaths){
EnumSet<SupportPath> enumPaths = EnumSet.noneOf(SupportPath.class);
stringPaths.stream()
.forEach(stringPath-> enumPaths.add(SupportPath.valueOf(stringPath.toUpperCase())));
return new Path(enumPaths);
}
}
Loading