안녕하십니까 영한님. 영한님 덕분에 JAVA Spring 기반 백엔드 개발의 시작을 할 수 있었고, 이제는 5개월 차 스타트업 백엔드 개발자로써 커리어를 시작하게 되었습니다. 현업에 와서는 오히려 취준생 때 보다 더 많은 고민을 하게 되느데요, 질문 1. 그중 최근에 CRUD의 각 상황별 API 응답을 어떻게 보내는게 적절할지, 질문 2. 그리고 ResponseDto를 어떻게 구성하는것이 적절할지에 대한 고민을 팀장님과 함께 하고있어서, 영한님의 생각을 여쭙고자 질문글을 올리게 되었습니다. 답변 해주시면 정말 감사할 거 같습니다! Q1. 생성, 조회 , 수정, 삭제 API의 응답을 각각 어떻게 보내시는지 여쭤보고 싶습니다. 가장 먼저 조회의 경우는 말 그대로 path의 Entity 및 관련된 Entity 정보를 조합하여 응답 DTO로 변환하여 보내고 있습니다. 그런데 나머지 Write Operation에 대한 응답을 어디까지 보내야 하냐가 이슈 입니다. 예를들면 엔티티의 생성의 경우 엔티티의 응답 DTO를 보내면 - 프론트에서 별도의 조회 API 호출 없이 바로 프론트가 화면에 뿌려줄 수 있으니깐 저는 생성의 경우에도 조회와 마찬가지로 Entity의 정보를 조합하여 응답 DTO로 변환하여 보내고 있었습니다. 그런데 이러한 부분이 Command Query Sperate 원칙에 어긋나는것 같아, 영한님께서는 혹시 생성한 Entity의 Key만 보내시는지, 아니면 Entity의 정보를 DTO로 변환하여 보내시는지 궁금합니다. 만약 Entity의 Id만 보내신다면 이후에 별도로 조회API를 호출해야 하고 그 또한 비용일텐데 이러한 부분은 어떻게 하시는지 여쭤보고 싶습니다. 이제 수정 API인데요, 제가 다룬 비즈니스 로직의 경우 수정 비즈니스 로직이 다양하고 , 각 비즈니스 로직의 경우 다뤄지는 Entity의 종류가 다른 경우였습니다. (중심 Entity는 동일하지만, 연관된 Entity를 누구까지 건드리냐의 차이) 그래서 하나의 통일된 응답으로 보내기 모호한 점이 첫 번째 이유이고, 애초에 수정 후에 프론트 화면에서 그 엔티티의 정보를 보여줄 필요가 없어서 라는 두번째 이유에 의해서 에초에 엔티티의 Id값도 보내지 않고 있었는데요, 이 수정 API의 응답을 영한님은 어떻게 진행하지는지 그 이유가 궁금합니다. 마지막으로 삭제의 경우는 정말, 프론트에게 보낼 응답이 없어도 되는 경우 라고 생각했는데요, 팀장님의 의견은 만약에 나중에 삭제한 Entity를 복구하는 요구사항이 추가되는것을 고려하여 Id 정도는 넘기자는 의견을 내어주셨습니다. 마찬가지로 삭제의 경우도 어떤식으로 수행하시는지 그 이유가 궁금합니다. Q3. 마지막으로 Entity의 ResponseDto의 필드를 어떤식으로 구성하시는지 궁금합니다 예를들면 저의 경우는 API는 프론트와 서버 간의 스펙이라고 생각하고, Entity의 단건조회의 경우는 단건 조회용 ResponseDto를, 전체조회의 경우는 전체 조회용 SummaryResponseDto를 별도로 만들어서 사용하고 있었습니다. 저희 팀장님 께서는 프론트쪽도 일을 해오시다가 , 백엔드쪽 분야로 전향하신 케이스 인데요, 그렇다 보니 어떻게 해야 프론트의 생산성이 올라가는지를 고려하시는 분이셨고, 팀장님의 생각은 서버에서 넘겨주는 응답에 일관성이 있어야 그 응답을 사용하는 프론트 측도 학습이 되고 놓치는 부분 없이 생산성이 올라간다 는 의견이셨습니다. 그래서 Entity별로 당장 사용하지 않더라도 가능한 모든 필드를 담은 ResponseDto를 하나만 만들고, 해당 ResponseEntity의 조합으로 각 API별 응답 Dto를 만들어서 사용하면 , 프론트 측 에서는 일관성 있는 응답값을 사용할 수 있다는 의견이셨습니다. 물론 이 방법이 네트워크 패킷의 양을 쓸데없이 증가시킨다는 것을 알고 계시면서도, 생산성에 큰 영향을 미치는 부분이라고 생각하셨습니다. 예를들어 다음과 같이 각 Entity의 응답 Dto의 조합별로 API의 ResponseDto를 만들 수 있습니다. ResponseDto{ UserDto{ id : 1, name : “aaa” … // User엔티티의 거의 모든 필드 } ItemDto{ id : 2, name : “bbb”, … // Item엔티티의 거의 모든 필드 } } 저는 이러한 부분에 대해 생각해 본 적이 없이, 그냥 제가 “해당 API를 호출하는 화면에서 필요한 정보들만을 담아 (혹은 여러 화면에서 쓰인다면 여러개를 고려) ResponseDto를 각각 만들어서” 넘겼는데요 영한님께서는 이러한 ResponseDto를 구성하는 부분에 있어서 상황별로 필드를 재구성 하여 ResponseDto를 정의하여 사용하시는 편 인지 (SummaryResponseDto 등의 별도 Dto에 사용될 Entity의 필드들을 풀어서 정의하시는지) 아니면 생산성을 고려하여 각 Entity별 Dto를 만들고, 이들을 조합하여 ReponseDto를 정의하시는 편 이신지 , 혹은 다른 규칙이 있으신지 궁금합니다. 물론 그렇다고 , 팀장님의 의견에서 전체조회시 사용하는 DTO와 단건조회시 사용하는 DTO가 동일하더라도, 전체조회 후 단건조회를 할때 단건조회 API를 호출하지 말고 기존 Front가 가지고 있는 값을 쓰지는 말자는 의견 이십니다 (단건조회API는 별도로 호출해야 한다) 그저 핵심은 프론트가 다루는 ResponseDto의 일관성을 위해서 입니다 (결론은 생산성을 위해) 긴글 읽어주셔서 감사합니다. 항상 건강하셨으면 좋겠고, 다음 강의들도 손꼽아 기다리고 있습니다!
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 문제에서 EC2 인스턴스는 NAT 게이트웨이 아웃바운드 인터넷 엑세스를 사용하지만~ 이라는 구절이 있습니다. 그렇기에 이미 EC2 보안그룹에서 인터넷으로 가는 아웃바운드 규칙은 잘 설정 되어있다고 생각했는데요 정답이 E. EC2 인스턴스에 대한 보안 그룹 연결에 대한 아웃바운드 규칙이 잘못 구성되었습니다 여서 왜 정답이 되는지 궁금해서 질문 남깁니다.
비전공자를 위한 풀스택 맛집지도 만들기 프로젝트!: Front, Back-end 그리고 배포까지
module.exports = { jwtsecret : "jwt_secret_key_9312", host : "3.38.61.157", user : "root", port : "3306", password : "~~~", detabase : "enrolment", }; 이렇게 하고 index.js를 해줬는데 선생님처럼 aws아이피는 안 뜨고 그냥 PS C:\Users\YOONJUNHO\Documents\Test\Node-Template-For-Food-Map-main> node index.js 2023-11-15 02:38:07 info: undefined - API Server Start At Port 3000 이것만 뜨는데 이것도 연결된건가요?
안녕하세요 redis-serve 실행까지는 잘되는데 vsCode 에서 yarn test 만 하면 꼭 아래와 같은 에러가 발생합니다. 기본포트로 할때는 되다가 6380 포트로 바꾼이후로 잘안되고 redis 에서도 바뀐포트로 연결하고 비밀번호까지 쳤는데 아래와 같은 에러가 발생합니다. 그리고 제소스가 에러가 나서 제공해주신 express 소스를 압축을 풀어도 같은 에러가 발생을 합니다. 혹시몰라 build 파일을 삭제하고 재빌드후 다시 켜도 아래와 같은 에러가 나는데 혹시 이유를 알 수 있을까요? ● POST /messages › responds with a success message Unhandled error. ([ErrorReply: ERR wrong number of arguments for 'auth' command]) at RedisSocket.<anonymous> (node_modules/@redis/client/dist/lib/client/index.js:396:14) at RedisSocket._RedisSocket_connect (node_modules/@redis/client/dist/lib/client/socket.js:166:18) at Commander.connect (node_modules/@redis/client/dist/lib/client/index.js:184:9) ● GET /messages › responds with all messages Unhandled error. ([ErrorReply: ERR wrong number of arguments for 'auth' command]) at RedisSocket.<anonymous> (node_modules/@redis/client/dist/lib/client/index.js:396:14) at RedisSocket._RedisSocket_connect (node_modules/@redis/client/dist/lib/client/socket.js:166:18) at Commander.connect (node_modules/@redis/client/dist/lib/client/index.js:184:9) ● Test suite failed to run thrown: "Exceeded timeout of 5000 ms for a hook. Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout." 18 | }); 19 | > 20 | afterAll(async () => { | ^ 21 | await client.flushDb(); 22 | await client.quit(); 23 | }); at Object.<anonymous> (app/index.test.ts:20:1) Test Suites: 1 failed, 1 total FAIL app/index.test.ts (101.064 s) ● POST /messages › responds with a success message Unhandled error. ([ErrorReply: ERR wrong number of arguments for 'auth' command]) at RedisSocket.<anonymous> (node_modules/@redis/client/dist/lib/client/index.js:396:14) at RedisSocket._RedisSocket_connect (node_modules/@redis/client/dist/lib/client/socket.js:166:18) at Commander.connect (node_modules/@redis/client/dist/lib/client/index.js:184:9) ● GET /messages › responds with all messages Unhandled error. ([ErrorReply: ERR wrong number of arguments for 'auth' command]) FAIL app/index.test.ts (100.278 s) ● POST /messages › responds with a success message Unhandled error. ([ErrorReply: ERR wrong number of arguments for 'auth' command]) at RedisSocket.<anonymous> (node_modules/@redis/client/dist/lib/client/index.js:396:14) at RedisSocket._RedisSocket_connect (node_modules/@redis/client/dist/lib/client/socket.js:166:18) at Commander.connect (node_modules/@redis/client/dist/lib/client/index.js:184:9) ● GET /messages › responds with all messages Unhandled error. ([ErrorReply: ERR wrong number of arguments for 'auth' command]) at RedisSocket.<anonymous> (node_modules/@redis/client/dist/lib/client/index.js:396:14) at RedisSocket._RedisSocket_connect (node_modules/@redis/client/dist/lib/client/socket.js:166:18) at Commander.connect (node_modules/@redis/client/dist/lib/client/index.js:184:9) ● Test suite failed to run thrown: "Exceeded timeout of 100000 ms for a hook. Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout." 18 | }); 19 | > 20 | afterAll(async () => { | ^ 21 | await client.flushDb(); 22 | await client.quit(); 23 | }, 100000); at Object.<anonymous> (app/index.test.ts:20:1)
당연히 구글링 해보셨져? 원하는 결과를 못찾으셨나요? 어떤 검색어를 입력했는지 알려주세요. - 검색해보았으나, 원하는 결과를 찾지 못했습니다. 검색어: 서비스 계층에서의 @Transactional 사용 기준 문제가 발생한 코드(프로젝트)를 Github에 올리시고 링크를 알려주세요. ``` java @Transactional public void write(Long postId , CommentCreate request) { Post post = postRepository.findById(postId).orElseThrow(PostNotFound:: new ) ; Comment comment = Comment.builder() .post(post) .author(request.getAuthor()) .password(request.getPassword()) .content(request.getContent()) .build() ; post.addComment(comment) ; } public void delete(Long commentId , CommentDelete request) { Comment comment = commentRepository.findById(commentId).orElseThrow(CommentNotFound:: new ) ; commentRepository.delete(comment) ; } ``` 서비스 계층에서 댓글 작성의 경우 @Transational 적용했지만, 삭제의 경우 적용하지 않았는데 강의에서 이 두 경우의 차이에 대해 이해해야 하고, 찾아보라고 이야기해주셨습니다. 지금까지 제가 공부한 내용에 비추어보면 서비스 게층의 생성 및 삭제의 경우모두 @Transcational 어노테이션을 항상 사용해 왔습니다. 왜냐하면, 수정 삭제의 경우 하나의 트랜잭션에서 시작 및 종료되어야 하기 때문이라고 생각했습니다. 구글링도 해보았는데, 납득할만한 이유를 찾지 못하여 호돌맨님 및 수강생분들에게 질문드립니다.
[6-3] CloudWatch 실습 강의를 보고 있습니다. WINDOWS와 MAC의 실습 환경이 달라 따라가기가 어렵습니다. WINDOWS에서 실습할 수 있도록 관련 자료를 PDF나 영상으로 만들어주셨으면 좋겠습니다. 실습을 따라하는 시간보다 실습을 진행하기 위해 구글링으로 찾아보는 시간이 더 오래 걸립니다.
학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! 질문과 관련된 영상 위치를 알려주면 더 빠르게 답변할 수 있어요 먼저 유사한 질문이 있었는지 검색해보세요 안녕하세요 선생님, 질문은 3가지 입니다. 1) 저는 문제를 딱 접했을때 분류모형을 써야할지, 예측모형을 써야할지 판가름을 정확하게 못하는 것 같습니다. 쉽게 판별하는 방법이 있을까요? 2) 이 문제의 경우 target인 output 컬럼을 train.head() 로 보면 0과 1로 구분되어있어서, 0 또는 1로 분류하는(분류모형) 것인가 생각했다가도 문제 맨위에서 참조해주는 예시에서 id,output 41,0.633 28,0.123 222,0.355 를 보면 output이 확률값으로 되어있어서 회귀모형을 사용해야하는 것인가? 라고 헷갈리곤합니다. 어디서 개념을 잡지 못하는 것일까요 3) 최종 예측을 할때 pd.DataFrame({'id':test_id, 'output':pred_proba[:,1]}).to_csv("00000.csv", index=False) output에 pred_proba 를 쓰셨는데 참조예시에서 확률값을 OUTPUT에 담았기 때문에 pred_proba를 사용한 것일까요? 그렇다면 output에 pred 를 담는 경우는 어떤 경우인지요?
자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
그리고 파일들을 보니까 다 빨간색으로 변해있더라고요 언제부터 변했는지는 모르겠어요. 우선 git 명령어 치기 전까지는 괜찮았어요 오류를 찾아보니까 해당 파일에 .git 파일을 지우고 다시 하라고 했던 것 같은데 그런 파일은 없더라고요 오류에 대해 잘못 찾았나 봐요 ㅎㅎ; ------------------- .git 폴더는 다시 보니까 숨긴 항목으로 숨겨져 있었네요.. .git 폴더 지우고 다시 처음부터 했더니 이상 없습니다!
학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! 질문과 관련된 영상 위치를 알려주면 더 빠르게 답변할 수 있어요 먼저 유사한 질문이 있었는지 검색해보세요 df = df.T df.head() mean_2001 = df[2001].mean() mean_2003 = df[2003].mean() a = sum(df[2001] > mean_2001) b = sum(df[2003] < mean_2003) print(a+b) 이렇게 작성하면 결과가 다르게 나오는데,, 어디서 잘못된 것일까요?
값을 찾을 때 $elemMatch 로 감싸줘야 둘 다 해당하는 값을 찾을 수 있다고 해주셨는데요. 반대로, $elemMatch 를 쓰지 않으면 or 조건으로 검색이 된다고 이해했습니다. 여기서 위와 같은 필터로 값을 검색 했을 때 username 이 Henry5522 이거나, title 이 ullam esse dolorem 인 모든 값이 필터 결과로 나와야 하는게 아닌가요?
await Promise.all([ comment.save(), Blog.updateOne({ _id: blogId }, { $push: { comments: comment } }), ]); 위 코드에서 comment.save() 는 정상적으로 동작해서 DB 에 입력이 되었지만 Blog.updateOne 은 알수 없는 오류로 실패가 떨어졌다면, 데이터에 싱크가 맞지 않을 수 있을거 같은데요. comment.save 했던걸 되돌리거나 하는 처리가 있어야 할 거 같은데, 어떻게 처리할 수 있나요?
읽는 시점에 populate 부분에 대한 시간도 최소화 하기 위해서 생성하는 시점에 user 의 최소한의 데이터를 넣어주고 있는거 같은데요. 이렇게 접근하면, 화면 중심으로 모델이 설계가 되는게 아닐까요? 요구사항이 바뀌거나, 다른 서비스에서 해당 API 를 사용하게 되면 매번 모델을 수정하고 데이터를 마이그레이션 해야 하는 이슈가 생길 수 있을거 같은데요. 어떻게 생각하시나요?