-
Notifications
You must be signed in to change notification settings - Fork 175
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
[Spring 지하철 경로 조회 - 3단계] 검프(김태정) 미션 제출합니다. #151
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
검프 안녕하세요!
미션 구현 잘 해주셨네요 :)
수정해 보면 좋을 것 같은 부분 코멘트 남겼어요!
궁금한 점 있으면 언제든지 질문 주세요 :)
List<Station> stations = stationDao.findAll(); | ||
List<Section> sections = sectionDao.findAll(); | ||
|
||
GraphPath<Station, DefaultWeightedEdge> path = makeDijkstraShortestPath(stations, sections).getPath(stationDao.findById(sourceId), stationDao.findById(targetId)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DefaultWeightedEdge
, WeightedMultigraph
, DijkstraShortestPath
같은 객체는 외부 라이브러리인 org.jgrapht
의 객체들인데요! 만약 나중에 org.jgrapht
대신 다른 라이브러리를 사용하게 되면 위 세 객체는 사용하지 못하게 될 것 같아요.
지금 Dao 처럼 최단 거리를 구하는 객체를 추상화 한 후 org.jgrapht
를 이용해 구현 하도록 분리하면 어떨까요?
나중에 다른 라이브러리 사용시 다른 구현체를 만들면 되도록 수정해보면 좋을 것 같네요 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return new PathResponse(StationResponse.listOf(shortestPath), (int) path.getWeight()); | ||
} | ||
|
||
private DijkstraShortestPath<Station, DefaultWeightedEdge> makeDijkstraShortestPath(final List<Station> stations, final List<Section> sections) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
말씀 주신대로 매 요청마다 graph 를 만들고 있어요.
이럴땐 보통 캐시를 사용하긴 하는데, 어떻게 구현해야할지 한번 고민해보고 검프의 의견을 들려주시면 좋겠어요 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이전에는 cached의존성을 따로 추가했어야하는데, 최근 스프링 프레임워크에서 기본적으로 제공한다고 해요.
그래서 SpringBootApplication에 @EnableCaching을 선언 후, findAll()에 "lines"라는 이름로 캐싱을 등록했고, 나머지 수정 로직에서는 lines를 삭제 후 다시 캐싱하도록 변경했어요.
여기서 의문점은,
캐싱은 어디까지해야 하나요?
현재는 lineDao 한부분에서만 캐싱을 하고있지만, 서비스가 커질수록 그 양도 많아질거같아요. 그로인해 애노테이션이 중복되는거 같아요. 하지만 DB 커넥션 수를 줄인다는 점에서는 되게 효율적인 전략인 거같은데, 실무에서는 어떻게 적용되고 있는지 알 수 잇을까요..?
저장된 캐시의 업데이트 혹은 삭제
스프링에서 지원하는 캐싱에는 총 2가지의 캐시 수정 애노테이션이 있다는 것을 알게됐어요.
@CacheEvict
, @CachePut
매번 캐시를 삭제 후 저장하는게 성능에 좋지 않을 것 같아 처음에는 @CachePut
를 사용하려 했는데, 제대로 캐시가 업데이트 되지 않는 것을 알게됏어요.
또한 @CacheEvict
을 단독적으로 썼을 때 캐시가 제대로 삭제되지 않아서 @CacheEvict(allEntries = true)
를 사용했어요.
일반적인 애노테이션과 옵션을 준 애노테이션의 차이가 있었을까요..?
디버깅
캐시의 경우 디버깅을 어디서 해야할지 감이 잡히지 않아요. 따로 저장소가 있는지는 알고있는데, 이를 아무리 찾아봐도 확인할 수 가 없어요 ㅠㅠㅠ
우선 ConcurrentMapCacheManager를 사용하여 Cache 가져오는 것 까지는 완료햇는데, 캐시가 포함하는 값을 값을 가져오기가 힘드네요.. 캐시는 그냥 저장 되어 있다~ 라고만 생각해도 무방한가요..?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
검프 안녕하세요!
피드백 반영하신 부분에 관하여 코멘트 남겼어요!
확인 부탁드릴게요 :)
} | ||
|
||
public List<Station> shortestPath(Station source, Station target) { | ||
GraphPath<Station, DefaultWeightedEdge> path = shortestPathStrategy.match(lines).getPath(source, target); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
domain 객체 내에 외부 라이브러리 객체인 GraphPath
와 DefaultWeightedEdge
가 들어와있네요 ㅠㅠ
Strategy 가 Path를 반환하도록 수정해보면 어떨까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 도메인 객체인 Patrh가 전략을 가지고 있으면서, 전략에 따라 책임의 구현이 달라진다 생각했어요. 도메인이 전략을 의존하고 있는게 자연스럽다 생각했어요.
그렇기 때문에 Strategy가 Path를 반환한다는 것은 잔략이 도메인을 의존하고 있다는 생각이 들어요..
우선 도메인에 외부라이브러리 의존을 제거 하라는 의도로 생각하여, 전략이 외부 라이브러리 의존을 하게 했어요.
추가적인 질문은 DM으로 남겼어요!
} | ||
|
||
@Cacheable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
List<Line>
을 캐시하는 것이 아니라 graph를 캐시하는건 어떠신가요?
학습로그@ExceptionHandler란?웹 요청은 리퀘스트를 통해 들어와요. 즉 메인 로직은 컨트롤러 메서드 실행시 동작해요. 이때, 일어나는 예외를 잡아줘요. @ExceptionHandler@RestController
@CrossOrigin(origins = "*", allowedHeaders = "*")
public class MemberController {
private MemberService memberService;
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
@PostMapping("/members")
public ResponseEntity createMember(@RequestBody MemberRequest request) {
MemberResponse member = memberService.createMember(request);
return ResponseEntity.created(URI.create("/members/" + member.getId())).build();
}
@ExceptionHandler(value = {InvalidInputException.class, DuplicateException.class} )
public ResponseEntity<String> inputValid(RuntimeException exception) {
return ResponseEntity.badRequest().body(exception.getMessage());
}
} 위와 같이, 컨트롤러 단 내부에서 예외를 잡아줄 수 있어요. 이때의 적용범위는 MemberController 한곳이에요. 사용법컨트롤러에서 일어날만한 예외를 하지만 컨트롤러가 많아지고, 예외가 많아지면 관리 포인트가 늘어나겠고, 코드량이 많아지겠죠..? 이때 사용하는 것이 @ControllerAdvice@RestControllerAdvice
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(value = {InvalidInputException.class, DuplicateException.class})
public ResponseEntity<String> unValidBinding(final MethodArgumentNotValidException exception) {
logger.error(exception.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exception.getMessage());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> unExpectException(final Exception exception) {
logger.error(exception.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(exception.getMessage());
}
}
이제 Handler단의 전반적인 예외를 잡을 수 있게 됐어요. 잠깐, 뭔가 이상하지 않나요? @ControllerAdvice ≠ @RestControllerAdvice분명, 제목을
즉, @RestControllerAdvice = @ControllerAdvice + ResponseBody 에요. 태그ExceptionHandler |
학습로그OSI 7계층이란?
계층이 나눠진 이유는?통신이 일어나는 과정을 단계적으로 파악하기 위함이에요. 또한, 7단계 중 특정한 곳의 에러가 다른 단계로 번지지 않게 하기 위함이에요. 얼마나 중요한가?최근에는 TCP/IP Model이 시장 점유에서 이겨, 그 중요성이 떨어지고 있어요. 하지만 이론적인 가치가 충분히 있고, 새로운 model이 나오더라도, OSI 7 Layer과 대비할 수 있기 때문에 여전히 중요해요. TCP/IP 모델의 과정은 이전 포스트에서 다뤘어요. 1. 물리(Physical) 계층네트워크 노드 간의 물리적 케이블 또는 무선 연결을 담당해요 encoding: 0과 1의 나열을 아날로그 신호로 바꾸어 전선으로 흘려보내요 decoding: 아날로그 신호가 들어오면 0과 1의 나열로 해석해요 물리적으로 연결된 두 대의 컴퓨터가 0과 1의 나열을 주고받을 수 있게 해주는 모듈이에요(함수) 하드웨어에서 구현인터넷 케이블, 무선 주파수링크, 핀 배치, 전압, 물리 요건 PHY 칩(모듈 구현) 2. 데이터 링크(Data Link) 계층장치간 신호를 전달하는 물리계층을 이용하여 연결된 네트워크 노드간에 데이터를 전송해요 framing이라는 기술을 사용하여 같은 네트워크에 있는 여러 대의 컴퓨터들이 데이터를 주고받기 위해서 필요한 모듈이에요(함수) 물리적 주소인 mac주소를 통해 통신을 하게돼요. 2개의 부계층네트워크 프로토콜을 식별하고 오류 검사를 수행하고, 프레임을 동기화하는 LLC(논리적 연결 제어, Logical Link Control), MAC(매체 접근 제어,Media Access Control)주소를 사용하여 데이터 전송 및 수신 권한을 정의하는 MAC의 두 부계층으로 구성돼요 하드웨어에서 구현랜카드, 스위치 3. 네트워크(Network) 계층데이터 링크에서 받은 프레임 내부에 포함된 IP주소를 기반으로 원하는 목적지로 전달하는 역할을 해요. 데이터를 논리적 주소인 IP주소를 통해 길을찾고(routing) 자신의 다음 라우터에게 데이터를 넘겨줘요(forwarding) 즉, 라우터의 기능 대부분이 여기 네트워크 계층에 자리잡아요. 다른 여러 라우터를 통한 라우팅을 비롯한 패킷 포워딩을 담당해요. 소프트웨어적으로 구현운영체제의 커널 4. 전송(Transport) 계층Port번호를 사용하여 도착지 컴퓨터의 최종 도착지인 데이터를 TCP의 예를 들면, 데이터 전달 보증, 순서를 보장 할 수 있는 이유는 3Way handshake과정이 있기 때문이에요. TCP 3 way handshake는 실제로 연결된게 아니에요. 연결이 됐나보다~처럼 논리적으로 연결이 된 것이에요.(물리적으로 연결된 랜선이랑은 느낌이 다름) 소프트웨어적으로 구현운영체제의 커널 5. 세션(Session) 계층이 이상의 계층에서는 모두 Data로 표현해요 2대기의 기기, 컴퓨터 또는 서버 간에 "대화"가 필요하면 세션(session)이라는 통신 채널을 만들어야해요. 세션을 열고, 데이터가 전송되는 동안 세션이 열려 있고, 작동하는지 확인하며, 통신이 종료되면 세션을 닫는 역할을해요. 채크포인트를 두어, 연결이 손실되는 경우 마지막 체크 포인트에서 연결 복구를 시도해요 즉, 동기화 기능을 제공해요 6. 표현(Presentation) 계층애플리케이션 계층에 대한 데이터를 준비해요. 다른 장치가 올바르게 수신되도록 데이터를 인코딩, 암호화 및 압축하는 방식을 정의해요. 또한, 반대로 세션 계층에 대한 데이터를 준비해요. 즉, 응용 계층의 데이터 표현에서 독립적인 부분을 나타내요. 일반적으로 응용프로그램 형식을 준비 또는 네트워크 형식으로 변환하거나 네트워크 형식을 응용프로그램 형식으로 변환하는 것을 나타내요.. 다시 말해 이 계층은 응용프로그램이나 네트워크를 위해 데이터를 표현해요. 7. 응용(Application Layer) 계층사용자에게 보여지는 부분이에요. 구글 크롬, 사파리 등의 웹브라우저와 줌, 스카이프 등의 응용프로그램이 대표적이에요. 이때 각 응용프로그램(프로세스)은 PORT번호를 부여받아요 결론네트워크 시스템은 소프트웨어 아키텍처인 Layered 아키텍처를 따라요. 즉, 네트워크 시스템은 하나의 커다란 소프트웨어라 볼 수 있어요. 결론적으로, OSI 7 Layer 모델은 거대한 네트워크 소프트웨어의 구조를 설명하는 것이에요. |
업데이트 시 캐싱 초기화 확인완료
안녕하세요 데이브 3번째 pr이네요..! 정확히 알고 기술을 사용해야한다는 것을 DM을 통해 배웠어요 ㅎㅎㅎ 배운점은 이후 추가할게요!! 좋은 피드백 감사합니다 😊😊 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
검프 안녕하세요!
피드백 반영하고 캐싱까지 적용해보느라 고생 많으셨습니다 👏
이번 미션은 여기서 마무리 할게요!
public List<Station> shortestPath(Station source, Station target) { | ||
return shortestPathStrategy.getVertexList(lines, source, target); | ||
} | ||
|
||
public int shortestDistance(Station source, Station target) { | ||
return shortestPathStrategy.getWeight(lines, source, target); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
this.lineDao = lineDao; | ||
} | ||
|
||
@Cacheable(value = "cache::shortestPath", key = "#sourceId.toString() + '::' + #targetId.toString()") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
적용하고 테스트 하느라 고생 많으셨습니다 👏
안녕하세요 데이브!! 좋은 주말입니다 😊
이번 미션은 전반적으로 구현이 되어있어, 구현한느데는 크게 시간이 들지 않았어요.
전반적인 테스트, 기능은 완료되어 우선 PR을 날려요. (이후 리펙토링 할 수 있는 부분들은 모두 리펙토링 해볼게요! )
미션을 진행하다 질문사항이 생겨 질문남겨요
빠른길을 찾기위해 전체 노드와 노드사이의 거리를 매번 초기화 해줘야하는건가요?
처음에는, source Station기준 상행, 하행역을 전부 찾아가며 등록을 하고, target을 찾으면 찾는 것을 끝내려 했는데, 그러다보니, 쿼리가 엄청 날라가더라고요..
어쩔 수 없이 첫 요청시 전체 역, 구간을 가져온 뒤, graph에 등록 후 라이브러리를 사용했어요.
사용자가 많아지면 전체 쿼리 조회 과정이 커질거라 예상되는데..
이런 경우 어떠한 전략을 세울 수 있을까요..?!
이전 피드백을 통해 많이 배울 수 있었던 것 같아요..!
이번에도 많은 피드백 부탁드려요 🙇🏻♂️