안녕하세요. 로드맵 수강하고 토이 프로젝트 진행 중인데 궁금한 점이 계속 나오네요.. 일반적으로 사용하는 표현 - 비즈니스 - 도메인 계층 구조에서 비즈니스 정책을 검증하는 위치와 관련해서 질문 드립니다. 예를 들어 사용자용 게시글 삭제 API는 현재 요청 사용자가 게시글의 작성자가 맞는지 검사합니다. 이 검증 작업을 서비스 계층에서 처리했습니다. public class PostService { private final PostRepository postRepository; // 게시글 삭제 기능 public void deletePost(Long postId, Long currentMemberId) { // 엔티티 조회 후 게시글 작성자가 현재 요청 사용자( currentMemberId )와 일치하는지 검사 Post post = postRepository.findById(postId); if (!post.getWriter().getId().equals(currentMemberId)) { // 작성자가 아니면 예외 발생 } post.delete(); } } 이 메서드를 사용자 API 컨트롤러에서는 잘 사용했는데, 관리자 API는 게시글 작성자 검증이 필요 없다 는게 문제였습니다. 사용자 및 관리자용 API 컨트롤러에서 호출하는 것은 결국 '게시글 삭제'라는 동일한 기능이므로 하나의 서비스 클래스에서 제공하는 게 맞다 판단하여 deletePost() 메서드에서 수행하던 유효성 검증 로직을 사용자 API 컨트롤러로 이관했습니다. public class PostController { private final PostService postService; // 실제 코드는 아니며 설명용 코드입니다. // 예를 들어 currentMemberId의 경우 실제론 @AuthenticationPrincipal 등을 통해 얻습니다. @DeleteMapping("/{postId}") public void delete(Long currentMemberId, @PathVariable Long postId) { Post post = postService.findPost(postId); if (!post.getWriter().getId().equals(currentMemberId)) { // 작성자가 아니면 예외 발생 } postService.deletePost(postId); } } 이렇게 구성하니 PostService 를 사용자 API 컨트롤러에서도, 관리자 API 컨트롤러에서도 사용 가능했지만.. 표현 계층에서 비즈니스 정책을 검증하니 구조가 이상한 것 같아서 질문 드립니다. '게시글을 삭제하려는 사용자가 게시글의 작성자인가?' 를 확인하는 작업은 비즈니스 정책에 의한 작업이라 생각합니다. 그러므로 서비스 계층에서 검사하는 게 맞다 생각되는데, 관리자 API에서 사용하기 불편하더라구요. 이런 경우 구조를 어떻게 가져가시나요? PostService 는 비즈니스 로직만 처리하고, PostUserService 같은 래퍼 서비스를 만들어 검증 부분을 처리할까 했는데, 썩.. 맘에 들진 않았습니다. 예시처럼 정책 검증을 컨트롤러에서 수행한다 가정하면, 제 코드의 경우 PostService.findPost() 메서드에 @Transactional(readOnly=true) 를 적용한 상태라 deletePost() 메서드에서 SELECT 쿼리를 또 실행합니다. @Transactional(readOnly=true) 은 정말 필요한 곳에만 선택적으로 적용하시나요? 아니면 읽기 전용 메서드는 별도로 구분하시나요? '서비스는 최대한 비즈니스 로직만 수행하고, 정책 유효성 검증은 다른 위치에서 진행하는 것이 좋다'라는 분들도 있던데, 보통 어떤 식으로 처리하시나요?
setupProxy.js 파일 내용을 아래처럼 바꾸시면 됩니다. 공식 문서 보니 설정 문법이 바뀌었나봐요. https://create-react-app.dev/docs/proxying-api-requests-in-development/#configuring-the-proxy-manually const { createProxyMiddleware } = require("http-proxy-middleware"); module.exports = function (app) { app.use( "/api", createProxyMiddleware({ target: "http://localhost:5000", changeOrigin: true, }) ); };
회원가입을 눌렀으나 회원가입이 안되고 요청이 가지 않는 것 같습니다. 에러메세지를 긁어서 확인 해보았으나 어디가 잘못된건지 모르겠고, 서버 부분의 콘솔을 확인해 보았으나 에러메세지가 나오지 않았습니다. 에러 메세지 이미지와 본문입니다. react_devtools_backend.js:4012 Error: Minified React error #31; visit https://reactjs.org/docs/error-decoder.html?invariant=31&args[]=object%20with%20keys%20%7Bsuccess%2C%20code%2C%20data%7D for the full message or use the non-minified dev environment for full errors and additional helpful warnings. at ka (react-dom.production.min.js:140:47) at react-dom.production.min.js:150:265 at Ml (react-dom.production.min.js:176:171) at Bi (react-dom.production.min.js:271:134) at Eu (react-dom.production.min.js:250:347) at wu (react-dom.production.min.js:250:278) at bu (react-dom.production.min.js:250:138) at pu (react-dom.production.min.js:243:163) at react-dom.production.min.js:123:115 at t.unstable_runWithPriority (scheduler.production.min.js:18:34 혹시나 ENV를 까먹었을까봐 다시 확인했지만 있었고, 아래는 서버쪽 터미널 이미지입니다. 감사합니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예/아니오) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) [질문 내용] 여기에 질문 내용을 남겨주세요. intelllij환경설정 전부 이상없이 설정하였고, 자바 11사용중인데 cmd 창에서 빌드하려니 계속 아래와 같은 에러가 뜹니다 ㅠㅠ 어떻게 해결할 수 있을까요?? Task :test FAILED Error: Could not find or load main class worker.org .gradle.process.internal.worker.GradleWorkerMain Caused by: java.lang.ClassNotFoundException: worker.org .gradle.process.internal.worker.GradleWorkerMain FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':test'. > Process 'Gradle Test Executor 3' finished with non-zero exit value 1 * Try: > Run with --stacktrace option to get the stack trace. > Run with --info or --debug option to get more log output. > Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 3s 7 actionable tasks: 1 executed, 6 up-to-date
[질문 템플릿] 1. 강의 내용과 관련된 질문인가요? 예 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예 3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예 [질문 내용] 안녕하세요. 엔티티 매니저 remove() 메서드 관련 질문이 있습니다. remove는 영속성 컨텍스트의 1차 캐시에서 엔티티를 삭제 하고, DB의 데이터를 delete 해주는 메서드로 알고 있습니다. 여기서 문제는 1차 캐시에서 엔티티를 삭제한다는 것이 "전체 데이터를 지우는 것(key:value)"인지 아니면 "해당 식별자에 할당된 데이터(value)"를 지우는 것인지가 궁금합니다. 위에서 그림에서 member를 remove 한 후에 remove된 member id를 받아 조회를 수행했습니다. 저는 1차 캐시에서 삭제된다고 알고 있었기에 select 쿼리가 실행될 것이라고 예상했으나 다음과 같이 select 쿼리가 실행되지 않았습니다. 제가 remove를 제대로 이해못한 것 같은데, remove를 수행하면 1차 캐시에서 바로 삭제되지 않는 걸까요?
안녕하세요 제로초님. 인피니티 스크롤 로딩 후 추가 데이터를 로딩하고 스크롤 위치를 유지하는 로직에 문제가 있어서 문의드립니다. 아래 코드에서는 setSize의 then문 안에 setTimeout으로 한 번 더 감싸주었는데 setTimeout이 없는경우 데이터가 불러와지기전에(스크롤의 높이가 늘어나지 않은 상태에서) setTimeout내부의 로직이 실행됩니다. 그로인해 스크롤의 높이는 기존높이 - 기존높이 로 scrollTop(0) 과 같은 상태가 되어버리는데요. setTimeout으로 0초의 딜레이를 주면 또 순서대로 동작을 합니다... 이런경우에 좋은 해결책이 있을까요? const onScroll = useCallback((values: positionValues) => { if (values.scrollTop === 0 && !isReachingEnd) { setSize((prevSize) => prevSize + 1).then(() => { setTimeout(() => { if (typeof scrollbarRef !== "function" && scrollbarRef?.current) { scrollbarRef.current.scrollTop(scrollbarRef.current.getScrollHeight() - values.scrollHeight); } }, 0); }); } }, []);
캐시 최적화 강의를 보고 난 뒤에 궁금증이 생겼습니다 app.use(express.static(path.join(__dirname, "../build"), header)); 를 통해 빌드된 것들에 대해 해더를 적용하는 것으로 이해했습니다. 근데 이렇게 빌드 된 것이 아니라 FE, BE 따로 나뉘어서 배포되어 서로 다른 도메인인 경우 어떻게 해야 하나요? BE는 static 자료가 없이 api만 보내는 기능을 하고 FE는 vercel이나 netlify 또는 다른 EC2로 배포되어 있는 경우를 말씀드립니다. app.use(header); 이렇게 하면 될까 했지만 이건 에러를 뱉어냅니다
보통은 새로 생성자를 만들어서 리턴값에 return store; 이런식으로 사용했었는데 따로 생성자를 만들지 않고 new ArrayList<>(store.values()) 라고 사용해도 위와 같이 그대로 리턴이 되는건가요? ArrayList의 타입은 따로 설정해주지 않아도 store 자체가 Map 형태를 가지고 있어서 알아서 Long,Member의 형태로 ArrayList가 생성이 되는건가요?
안녕하세요! 아래 코드 추가 후에 api를 요청하면 java.lang.NoClassDefFoundError: javax/persistence/Transient 에러가 뜨더라고요. @Bean Hibernate5Module hibernate5Module() { return new Hibernate5Module(); } 그래서 뭐가 문제인지 확인해봤는데요. 아마도 버전 이슈인 것으로 생각이 되어서 질문을 드립니다. 저는 지금 spring boot 3버전으로 실습을 따라가고 있고, 확인해보니까 spring boot 3버전에는 hibernate 6이 쓰이고 있더라고요... jackson-datatype-hibernate 라이브러리는 아직은 hibernate 5까지만 지원하는 것 같은데... 혹시 jackson-datatype-hibernate 를 대체할 수 있으면서 spring boot 3 에서 돌아가는 라이브러리가 있을까요??? 아니면 라이브러리를 사용하지 않고, hibernate5module의 역할을 구현할 수 있을까요?
야생형수강 순서 -> 관련 답변은 이런데 ---- 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 스프링 핵심 원리 - 기본편 현재 강의 모든 개발자를 위한 HTTP 웹 기본 지식 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 그리고 다음 순서로 듣습니다. 1. 실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발(빠르게 영상을 보면서 코드만 따라하기) 2. 자바 ORM 표준 JPA 프로그래밍 - 기본편 3. 실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발(진지하게 복습) 4. 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화 여기까지 진행하면 토이프로젝트를 진행하실 수 있을거에요. 그리고 이후에 다음 내용을 들이시면 됩니다. 5. 실전! 스프링 데이터 JPA 6. 실전! Querydsl +db 접근 강의 출시 전 답변이였고, db강의관련 순서는 mv2 다음에 2개 들으라고 적혀있었구요 또 다른 답변에요! mvc 1편 강의마무리에서 야생형을 소개할때는 추천 학습 방법 스프링 입문과 스프링 핵심 원리를 듣고 나면 스프링으로 개발하는 가장 중요한 기본 지식을 쌓은 상태가 됩니다. 그래서 바로 스프링 부트와 JPA 실무 완전 정복 로드맵에 들어가는 것도 좋은 선택입니다. 스프링을 실무에서 어떤 식으로 활용해서 개발하는지 먼저 배워두고, 이후에 스프링 완전정복 로드맵를 통해 스프링 MVC나 스프링 데이터 접근 기술 같은 부분은 더 깊이있게 학습하시면 됩니다. 라고 하셔서요! 문서에도 그래서 저는 입문,기본,http 요청,mvc1편 다 듣고-> 실전해보고 나서 되돌아오려고 실전편 1먼저 듣고 있는데 궁금해져서 물어봅니다 뭐가 맞는건지.. 답변과 강의에서의 안내가 달라서 여쭤봅니다
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= '실전! 스프링 부트와 JPA 활용1' 강의를 수강하기 위해 스프링 부트 스타터에서 프로젝트를 생성하려고 합니다. 그런데 Project에서 gradle을 선택하려고 하는데 gradle-groovy와 gradle-kotlin 이렇게 나뉘어지는데 무얼 선택해야 하나요?
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. const getPost = () => { axios.get('http://localhost:3001/posts').then((res) => { console.log(res); }) } axios.get 하면 개발자도구에서 네트워크-> posts 2번 불러오는데 이유를 알고싶습니다.
join을 해서 가지고 오면 이렇게 데이터가 중복이 되면서 2개로 뻥튀기가 된다고 설명을 해주셨는데요. 윗줄과 아랫줄이 완전히 동일한 것은 아닌데 이것을 왜 중복으로 보고 뻥튀기라 하는지 이해가 잘 가지 않습니다! 제가 나름 이해하기로는 예를 들어 위에 예시로 든 Order가 OrderA 라고 했을 때, 지금 찾고 싶은건 OrderA 라는 Order 하나인데 그 안에 OrderItem이 각각 서로 다른게 2개 들어있어서 OrderA-Boo1 , OrderA-Book2 이렇게 OrderA가 두 개가 조회돼서 뻥튀기라고 보는걸까요? 그런데 이러면 완전히 중복은 아닌거 같기도 하고.. 어떻게 이해하는 것이 좋을지 궁금합니다!
DetailVIew 및 ListView 에서 as_view() 할때 model, template_name_field, template_name 등등 상속으로 받은 클래스 변수를 초기화 해줍니다. 여기서 질문은 제가 클래스를 만들어 위 내용에 동일한 클래스 변수 생성하여 다중 상속하고 classmethod를 선언하여 메소드에 상속 된 변수를 선언할려고 하는데 코어에 as_view() 처럼 무엇이 상속되었는지 표시가 나오지 않습니다. 해당 내용에 대한 해결 방법에 대해 질문드립니다. 아래 내용은 스크린샷 입니다. 코어에서 as_view() 제가 만든 함수