블로그

Inje Lee (소플)

인프런 단일 강의로 수강생 만 명을 넘기기까지 (6년간의 기록)

(이 글은 제 웹사이트에 작성한 글을 그대로 가져온 것입니다.)원문 링크: https://www.soaple.io/post/9/%EC%9D%B8%ED%94%84%EB%9F%B0%20%EB%8B%A8%EC%9D%BC%20%EA%B0%95%EC%9D%98%EB%A1%9C%20%EC%88%98%EA%B0%95%EC%83%9D%20%EB%A7%8C%20%EB%AA%85%EC%9D%84%20%EB%84%98%EA%B8%B0%EA%B8%B0%EA%B9%8C%EC%A7%80안녕하세요, 소플입니다.기존 티스토리 블로그를 제 웹사이트로 통합하고 처음으로 이렇게 글을 남기네요ㅎㅎ이번 글에서는 저에게는 꽤 의미있었던,인프런 단일 강의로 수강생 만 명을 넘기기까지 6년 동안의 과정을 한 번 적어보려고 합니다.글이 꽤 길지만 관심을 갖고 재미있게 읽어주셨으면 좋겠고,개발관련 강의 제작이나 소프트웨어 교육에 관심이 있는 분들에게 조금이라도 참고가 되길 바랍니다 😀 '소프트웨어 교육'이라는 꿈먼저 잠시 제 어릴 때 이야기를 해보려고 합니다.저는 어릴 때부터 컴퓨터에 관심이 많았고, 일찍부터 프로그래밍을 배우고 싶어했습니다.하지만 지방에 살았기 때문에 당시 다녔던 동네 컴퓨터 학원에서는 프로그래밍을 제대로 배울 수 없었습니다.학원에는 컴퓨터 자격증 반이 대부분이었고 프로그래밍을 배우려고 하는 학생이 거의 없었기 때문이죠.그래서 제대로 된 교육 과정도 없었고 가르쳐 줄 선생님도 턱없이 부족했습니다.저에게는 당시 배우고 싶었던 것을 배우지 못한 상실감이 꽤 컸습니다.그래서 제가 나중에 어른이 되면, 프로그래밍을 배우고 싶어하지만 여건이 안되는 학생들에게 양질의 소프트웨어 교육을 제공해주고 싶다는 생각을 하게 됐습니다.그렇게 시작된 생각은 저에게 소프트웨어 교육이라는 꿈을 갖게 만들었습니다.그리고 그 꿈은 점점 더 커져서 현재 제 인생의 최종 목표인 소프트웨어를 전문적으로 교육하는 학교를 설립하는 것이 되었습니다. 첫 오프라인 강의 (2017년, feat. Python)때는 바야흐로 2017년 말이었습니다.우연한 기회로 대전에서 3일 과정으로 파이썬 입문 강의를 하게 되었습니다.제 인생 첫 오프라인 강의였기 때문에 정말 두렵고 떨리는 마음으로 강의를 준비했습니다.그래서 아래와 같이 열심히 강의 자료를 만들었고, 저 때 지금의 제 강의 템플릿이 완성되었습니다.강의를 하기 전날 대전에 내려가서 숙소에서 계속해서 강의 연습을 했던게 아직도 기억납니다.강의 당일에는 너무 긴장해서 일찍 잠에서 깼고, 이른 시간에 강의장에 도착해서 기념 사진을 찍었습니다.당시 전체 수강생은 약 20명 정도였습니다.수강생중에는 흰 머리의 나이 지긋하신 아버님도 계셨고,재수를 해서 수능을 마치고 온 학생도 있었습니다.그렇게 제 인생의 첫 오프라인 강의를 시작하게 되었고,3일 동안 하루 8시간씩 정말 열심히 강의를 진행했던 기억이 납니다.강의가 끝나고 수강생 분들에게 메일로 실습 코드를 보내드렸는데,답장으로 좋은 말씀들을 너무 많이 해주셔서 정말 보람을 느꼈던 것 같습니다.그렇게 저는 교육이라는 것이 정말 보람찬 일이라는 것을 몸소 깨닫게 되었고,제 꿈에 더 확신을 가지게 되었습니다. 우연히 찾아온 기회 (2018년)2018년에는 제가 본격적으로 프리랜서 개발자로 활동하기 시작했습니다.프리랜서 프로젝트를 구하고 있던 와중에, 제가 병역특례를 했던 회사 대표님의 소개로 N사의 모바일 앱 개발 프로젝트를 수행하게 되었습니다.당시 N사의 CTO님과도 미팅을 할 기회가 생겼는데, 소프트웨어 교육이라는 공통 관심사로 많은 이야기를 나눌 수 있었습니다.그리고 CTO님께서는 구름 류성태 대표님을 소개해주셨습니다.소프트웨어 교육에 관심이 많으니 아마 서로 도움이 될 수 있을 거라고 하시면서 말이죠.그렇게 처음으로 구름과의 인연이 시작되었습니다.2018년 한 해 동안 저는 프리랜서 프로젝트를 주로 하면서,가끔 오프라인 강의도 하고 틈틈이 유튜브에 동영상 강의를 올리면서 보냈습니다.  첫 동영상 강의를 제작하다 (2019년, feat. 구름 에듀)당시 구름에서는 구름 에듀라는 강의 플랫폼을 만들어서 한창 키워나가고 있었습니다.강의 플랫폼의 초기에는 다양한 컨텐츠를 확보하는 것이 중요합니다.그래야 많은 사람들이 들어와서 강의도 듣고 결제도 할 것이기 때문이죠.그렇게 시간이 될 때 한 번씩 구름과 미팅을 하면서,먼저 AWS 강의와 리액트 강의를 제작해보기로 결정을 하게 되었습니다.AWS 강의는 기존에 제가 오프라인에서 강의를 할 때 만들어둔 자료가 있었기 때문에 그걸 사용해서 조금 빠르게 제작할 수 있었고,리액트 강의는 커리큘럼부터 강의자료까지 다 처음부터 만들어야 했습니다.기존에 유튜브 채널에 강의 동영상을 가끔 올리긴 했지만,제대로 된(?) 동영상 강의 제작은 처음이었기 때문에 설레기도 하고 걱정이 되기도 했습니다.당시 제가 프리랜서 프로젝트를 하느라 바빴기 때문에 남는 시간에 틈틈이 강의 내용을 구성했고,본격적인 강의 제작은 2019년 중순이 되어서야 할 수 있었습니다.그렇게 약 2달 동안 강의 자료를 만들고 녹음하고 편집하는 과정을 계속 반복했습니다.(참고로 제 모든 강의는 스튜디오가 아니라 조용한 시간에 집에서 녹음합니다🤣)그리고 2019년 6월, 구름 에듀에 제 인생 첫 동영상 강의인 처음 만난 AWS와 처음 만난 리액트가 출시되었습니다.보시는 것처럼 강의를 처음 출시 했을 때는 비싸진 않지만 유료로 판매했었습니다.플랫폼 입장에서 무료 컨텐츠는 초기에 사람들을 끌어모으는 역할은 하지만, 직접적인 수입원이 되지는 못합니다.그래서 결국은 컨텐츠를 유료로 판매해야 플랫폼은 수수료를 가져갈 수 있고, 강의자는 강의로 인한 수익을 창출하면서 공생할 수 있는 것이죠. 유료에서 무료로하지만 당시 저는 유료 강의에 대해 조금은 부정적인 생각을 갖고 있었습니다.어릴 때의 제 모습을 계속 떠올리면서, 어린 친구들이나 학생들은 돈이 별로 없기 때문에 단돈 1~2만원의 강의료도 부담이 될 것이라고 생각했기 때문입니다.또한 저 스스로도 아직 강의 컨텐츠에 대해서 돈을 받고 팔 정도의 자신은 없었습니다.수강생과 입장을 바꿔서 '내가 학생이라면 이 정도 강의를 이만큼의 돈을 내고 들을 것인가?' 라고 생각해봤을 때,당시 제 마음속의 대답은 '아니요' 였습니다.그래서 유료 강의를 출시한 이후에도 계속 저런 생각들이 들었고,고민 끝에 구름측에 양해를 구하고 강의를 무료로 전환하게 되었습니다.많지는 않았지만 당시에 무료로 전환하기 전까지 강의를 구매해주신 분들이 계셨습니다.규정상 따로 환불을 해드리지는 못했는데, 만약 이 글을 보고 계신다면 제 메일(inje@soaple.io)로 연락을 주시기 바랍니다.늦었지만 제 인프런 강의를 무료로 수강하실 수 있도록 쿠폰을 보내드리겠습니다🥹그렇게 구름 에듀에 출시한 강의를 모두 무료로 전환하고나니 몇 가지 변화가 생겼습니다.먼저 구름에서 주최하는 개발 프로그램에 참여하는 분들에게 제 강의를 무료로 제공할 수 있게 되었습니다.그리고 그런 프로그램에 참여하는 분들을 포함해서 수강생이 빠르게 늘기 시작했습니다.수강생이 늘면서 강의에 대한 평가도 늘기 시작했고,그 과정에서 강의에 대한 수강생 분들의 긍정적인 피드백을 받으면서 저도 힘을 받을 수 있었습니다.그렇게 시간이 흘러 현재까지 구름 에듀에서 처음 만난 AWS 1,233명, 처음 만난 리액트(v1) 2,040명, 처음 만난 리액트(v2) 328명의 누적 수강생을 달성할 수 있었습니다. 영어로 만들어서 달러를 벌어보자 (2020년, feat. Udemy)수강생도 꽤 늘어나고 강의에 대한 긍정적인 평가들을 받다보니 제 강의에 대해 조금 자신감이 생겼습니다.그리고 저도 지속적으로 강의를 제작하기 위해서는 강의로 수익을 창출하는 것이 필요하다는 생각을 하게 되었습니다.하지만 그때까지도 한국에서 유료 강의를 판매하는 것에 대해서는 제 마음이 내키지 않았고,영문판을 제작해서 유데미(Udemy)에 올려보면 어떨까? 라는 생각을 하게 되었습니다.당시 유데미는 영어로 된 강의가 대부분인 전세계를 대표하는 강의 플랫폼이었기 때문에,인기 강의의 수강생 수나 매출이 한국 플랫폼들과는 비교도 안 될 정도로 컸습니다.그래서 제 강의가 아주 조금만 어필이 되어도 꽤 큰 돈을 벌 수 있을 것이라고 생각했습니다.그렇게 달러를 벌어들이는 꿈을 꾸며 처음 만난 리액트 강의의 영문판을 제작하기 시작했습니다.영문판을 제작하면서 가장 힘들었던 점은 역시 언어의 장벽이었습니다.한글로는 쉽게 설명할 수 있는 부분들을 영어로는 어떻게 설명해야 할지 몰라서 참 어려웠던 것 같습니다.그래서 번역기와 영어권에서 많이 사용하는 표현들을 찾아가며 꽤 오랜 기간에 걸쳐 강의를 완성했습니다.그리고 2020년 7월, 유데미에 처음 만난 리액트의 영문판인 First met React 강의가 출시되었습니다.(당시 유데미에 출시된 강의 이미지를 분명 캡처해뒀는데 못찾겠네요ㅠ)강의를 출시하고 처음에 수강생을 모으기 위해 레딧에 홍보를 해보기로 했습니다.그래서 레딧의 리액트 서브레딧에 3일 동안만 등록 가능한 무료 쿠폰을 뿌렸습니다.그랬더니 운좋게 아래와 같이 Upvote를 많이 받아서 Hot에 올라가게 되었습니다.그렇게 레딧 홍보를 통해 초기 수강생을 꽤 모을 수 있었습니다.하지만 무료 쿠폰으로 등록한 수강생들이 대부분이었기 때문에 수익은 거의 없었습니다 😂그러다가 어느 날 자려고 누워서 유데미 수강생을 확인하는데, 갑자기 새로고침 할 때마다 수강생이 50~70명씩 늘어났습니다ㄷㄷ그래서 깜짝 놀라서 이게 도대체 무슨 일인지 확인해봤더니, 인도에서 수강생들이 엄청나게 몰려오고 있었습니다.누군가가 인도의 유데미 무료 쿠폰 코드를 공유하는 사이트에 제가 레딧에 올린 쿠폰 코드를 올린 것이었습니다.그렇게 강의가 출시된 7월에만 6,243명이 등록했는데,그들은 대부분 무료 쿠폰을 통해 유입된 수강생이었고 인도 사람들이 아주 큰 비중을 차지하고 있었습니다.아래 그림에서 인도에 그려진 큰 원이 보이시나요?ㅎㅎ이후로는 거의 수강생이 늘지 않았고 가끔씩 유료로 강의를 구매하는 수강생들이 있긴 했지만 절대적인 수는 얼마 되지 않았습니다.이후 한 동안 강의를 관리하지 못했더니 약 2년 전쯤 강의가 내려가게 되었고,누적 수강생 7,434명과 누적 수익 $46.18를 끝으로 달러를 벌기 위한 여정이 마무리 되었습니다. 처음으로 개발 서적을 집필하다 (2021년, feat. 한빛미디어)유데미에서의 뼈아픈 실패(?)를 경험하고, 2021년에 저는 1인 법인을 설립하게 됩니다.소프트웨어 교육과 관련된 활동을 꾸준히 해왔지만 그게 저의 본업은 아니었습니다.본업은 프리랜서 개발을 하면서 동시에 저만의 서비스를 개발하는 것이었죠.개인사업자로 4년 동안 프리랜서 개발을 해왔었는데, 2021년에 여러가지 상황이 겹치면서 법인을 설립하게 되었습니다.법인은 소프트웨어 교육과 관련된 것은 아니었고, 온전히 저만의 서비스를 위한 것이었습니다.(이 부분도 스토리가 길어서 기회가 된다면 나중에 다른 글에서 다뤄보도록 하겠습니다😃)법인을 설립하고 정신없이 서비스를 개발하던 중에 아래와 같이 출판사로부터 메일을 받게 되었습니다.당시 구름 에듀에 있던 제 리액트 강의를 꽤 많은 분들이 수강해주셨는데,출판사에서도 제 강의에 관심을 가지고 연락을 주신 것이었습니다.메일을 받고 나서 저는 정말 큰 고민에 빠졌습니다.개발자로서 개발 서적을 집필하는 것은 제가 언젠가는 꼭 이뤄보고 싶은 일 중에 하나였기 때문에 설레기도 했지만,법인을 설립하고 본격적으로 판을 벌려놓은 상태라서 '과연 책을 집필할 시간이 될까?'라는 고민이 들었습니다.당시 제가 책을 집필해본 적은 없었지만, 주변에서 책을 쓰는게 정말 힘들고 고통스럽다는 이야기들을 꽤 많이 들어왔기 때문에 더 고민을 했던 것 같습니다.하지만 정말 좋은 기회라는 생각이 계속 들었고, 처음 만난 리액트는 이미 강의로 제작되어 있는 상태였기 때문에 '책을 집필하는 과정도 조금은 수월하지 않을까?'라는 무식한(?) 생각으로 출판사와 계약을 하게 되었습니다.그렇게 1년 동안 개발도 하면서 틈틈이 책을 썼는데, 강의 컨텐츠가 어느정도 있었지만 책을 쓰는 과정은 전혀 수월하지 않았습니다😂마지막에는 정말 남은 힘을 다해서 꾸역꾸역 한 글자 한 글자씩 써내려갔던 것 같습니다.그렇게 해서 2022년 5월에, 소문난 명강의 시리즈로 소플의 처음 만난 리액트가 출간되었습니다.약 1년 동안 책을 집필하면서 느낀 점은,정말 힘든 과정이라는 것과 스스로 동기부여가 되어야만 할 수 있는 일이라는 것입니다.사실 개발 서적은 트렌드도 빨리 바뀌고 독자층도 한정되어 있기 때문에 책으로 인해 큰 수입을 얻기는 어렵습니다.하지만 저자의 이름을 알리거나 오프라인/온라인 강의를 하는 경우에는 꽤 도움이 되는 것 같습니다.그만큼 책을 출간한다는 것 자체가 스스로에게 큰 동기부여가 되지 않으면, 다른 데서 집필을 무사히 마칠 정도의 동기를 얻기가 어렵습니다.그래도 저는 오래 전부터 제가 꼭 이루고 싶은 일 중에 하나였기 때문에 무사히 책을 집필할 수 있었던 것 같습니다.그렇게 출간된 제 책은 나름대로 자리를 잘 잡고 꾸준히 판매가 되었습니다.처음에는 1쇄만이라도 다 팔려서 재고는 안 남았으면 좋겠다고 생각했는데,출간된 지 1년 반이 지난 현재 3쇄까지 거의 다 판매되었고 현재 개정판 출간을 앞두고 있습니다. 드디어 인프런에 첫 강의를 업로드하다 (2022년, feat. 인프런)제 책은 이미 강의로 제작된 내용을 기반으로 하는 책이었기 때문에,처음 출판사와 계약을 할 때부터 책이 출간되는 시점에 맞춰서 강의도 새롭게 리뉴얼 하는 것으로 정했었습니다.그래서 책을 거의 다 집필한 시점에 처음 만난 리액트 v2 강의를 제작하기 시작했습니다.기존 강의가 2019년에 제작되었기 때문에 그 사이 리액트에도 많은 변화가 생겼습니다.클래스 컴포넌트를 주로 사용하던 방식에서 훅과 함께 함수 컴포넌트를 사용하는 방식이 사실상 표준으로 자리잡게 되었습니다.그래서 새로운 버전의 강의에서는 기존 강의의 내용을 업데이트 하는 것과 동시에 책에 들어간 내용과 동일하게 새로운 내용들도 추가하게 되었습니다.그리고 제가 말하는 속도가 느린편이라서 강의가 졸리다는 의견도 꽤 있었기 때문에,새로운 버전에서는 말을 의도적으로 빠르게 하게 되었습니다🤣(지금 강의 속도가 조금 빠르다고 느끼는 분들도 간혹 계시지만 대부분 만족하시는 것 같습니다ㅎㅎ)그렇게 열심히 새로운 버전의 처음 만난 리액트 강의를 제작하게 되었고,구름 에듀, 인프런, 유튜브 등에 모두 업로드 하게 되었습니다.특히 제가 인프런에는 강의를 처음으로 올리는 것이었는데,강의를 올리면서 '드디어 이제서야 인프런에 강의를 올리는구나.' 라는 생각을 하게 됐습니다.왜냐하면 과거에 인프런에서 저에게 먼저 강의 제작을 제안한 적이 있었기 때문입니다.당시 인프런도 본격적인 성장세를 타고 있었던 시점이라 아마 다양한 강의 컨텐츠들이 필요했을 겁니다.그래서 저에게 처음에 리액트를 주제로 유료 강의 제작을 제안해주셨는데,그때까지도 저는 한국에서 유료 강의를 출시하는 것에 대해 마음의 결정을 내리지 못한 상태였습니다.그래서 MD님께 그러한 제 가치관에 대해서 잘 말씀드리고,향후 기회가 된다면 무료 강의를 제작해보겠다로 말씀드렸습니다.그리고 당시 위 이메일 내용처럼 지식공유자 신청만 미리 해두었습니다.그렇게 시간이 흐르고 흘러 2년 가까이 지나서야 인프런에 첫 강의를 출시하게 된 것이죠.어찌됐든 늦었지만 인프런과의 약속은 지켰다고 생각합니다😀그렇게 인프런에 강의를 출시하고 메인 페이지에 꽤 여러번 노출이 되었습니다.그 덕분에 수강생 수도 굉장히 빠르게 증가하였고, 2022년 말에는 아낌없이 주는 나무상까지 수상하게 되었습니다.제 리액트 강의가 2022년에 수강신청 수가 가장 많은 무료 강의였다고 합니다.그렇게 책도 출간하고 강의도 많은 분들에게 사랑을 받으면서 정말 뿌듯하게 2022년을 마무리했던 기억이 납니다. 수강생 10,000명을 달성하다 (2023년, 현재)어느덧 시간이 흘러 인프런에 리액트 강의를 출시한지 거의 1년 반이 다 되었습니다.그 사이 제 책과 강의는 더 많은 분들에게 관심을 받게 되었습니다.그래서인지 구글과 네이버에서 '리액트 강의'라고 검색하면 제 강의가 가장 먼저 나오게 되었습니다.그리고 강의 컨텐츠가 여기저기 수출(?)되기 시작했습니다.특히 프론트엔드 개발에 입문하는 분들이 제 리액트 강의를 학습하면서 개인 블로그에 많이 정리하셨던 것 같습니다.(붕어빵 그림은 제가 정말 한땀한땀 그렸는데 수출되어서 뿌듯하네요ㅎㅎ)아무튼 그렇게 책을 통해서도 제 강의를 접하고 검색을 통해서도 제 강의를 접하는 분들이 늘어나면서,최근에 드디어 단일 강의로 수강생 10,000명을 달성하게 되었습니다 🎉당시 스스로 기념하기 위해서 화면을 캡처해두었습니다 😎어떤 분들에게는 별거 아닐 수도 있지만,저에게는 첫 오프라인 강의를 시작하고 지난 6년 동안의 노력이 점점 결실을 맺어가는 것 같아서 굉장히 큰 의미가 있었습니다.그리고 많은 수강생 분들께서 남겨주신 소중한 수강평 또한 큰 힘이 되었습니다.이 글을 통해서 수강생 분들에게 다시 한 번 감사의 말씀을 전합니다!😀(정말 재미있게 리뷰를 남겨주신 휴식중인 오리님ㅎㅎ 혹시 휴식 끝나셨나요?) 유료 강의를 출시하다책도 어느 정도 팔리고 무료 강의로도 꽤 많은 수강생을 달성하고 나니,제 강의 컨텐츠에 대한 자신감이 어느정도 생기게 되었습니다.그리고 무료 강의를 운영하면서 느꼈던 여러가지 장단점으로 인해,올해에는 유료 강의를 한 번 출시해보기로 결정했습니다.그래서 현재 처음 만난 리덕스와 엊그제 공개 된 따끈따끈한 처음 만난 AWS까지 총 2개의 유료 강의를 제작하게 되었습니다.강의가 아주 많이 판매되지는 않고 있지만,구매해주시는 분들이 있다는 사실에 감사함과 뿌듯함을 동시에 느끼고 있습니다 😀 강의를 제작하면서 얻은 것들이렇게 지난 6년 동안의 이야기를 한 번 정리해보았습니다.저는 교육에 꿈이 있었기 때문에 시작한 것이지만,전체 개발자 분들 중에서 교육에 관심을 갖고 뛰어드는 비율은 현저하게 낮은 것 같습니다.회사일만 제대로 하기에도 바쁘기도 하고, 강의를 제작하는 것이 생각보다 어렵고 시간이 많이 걸리기 때문입니다.제가 강의를 제작하면서 얻은 것들은 다음과 같습니다.흩어져 있던 지식들을 체계화 할 수 있는 기회헷갈렸던 개발 관련 지식을 다시 한 번 복습하며 이해할 수 있는 기회코드 리뷰를 하거나 다른 개발자에게 설명을 할 때 더 쉽게 설명할 수 있게 됨돈으로는 살 수 없는 아주 큰 보람과 성취감저는 기억력이 그리 좋지 않아서 정리를 하면서 지식을 익히는 타입인데,그래서인지 강의를 제작하는 과정이 저에게는 개발 지식을 습득하는데 큰 도움이 되었던 것 같습니다.앞으로 강의를 제작할 계획이 있거나 소프트웨어 교육에 관심이 있는 분들은 참고하셨으면 좋겠습니다😀 앞으로의 계획 (2024년 ~)이제 2023년이 2달도 채 남지 않았습니다.저는 내년 계획을 아직 제대로 세우진 않았지만,아마도 새로운 강의를 적어도 하나 정도는 제작하고 오프라인 강의도 한 번은 하지 않을까 싶습니다.그리고 무엇보다도 제가 개발하고 있는 서비스가 어느정도 자리를 잡았으면 좋겠네요ㅎㅎ여러분들도 내년에 어떤 걸 공부하고 어떻게 성장할 것인지,어떤 새로운 일들을 해볼지 지금부터 계획을 세워보시면 좋을 것 같습니다!👏긴 글을 모두 읽어주셔서 감사드리며, 마지막으로 강의 쿠폰과 약간의 홍보를 하면서 마무리 짓겠습니다ㅎㅎ 광고 (AD)강의 50% 할인 쿠폰 (~ 2023년 12월 31일 까지만 유효)처음 만난 리덕스 🔗쿠폰 코드: 13533-912257c568a9처음 만난 AWS 🔗쿠폰 코드: 13534-3be05f545ba4소플이 만든 프론트엔드 지식 포털 FrontOverflow 🔗소플 웹사이트 🔗 그리고 개발 공부를 하고 싶지만 어려운 환경에 있는 분들은,주저하지 마시고 언제든지 아래 제 이메일 주소로 연락주세요.제가 도움을 드릴 수 있는 부분이 있다면 적극적으로 도와드리겠습니다! 😀inje@soaple.io

게임 개발 기타인프런지식공유10K강의리액트소프트웨어교육

에리얼

인프런 비즈니스 대시보드, 어떻게 써야할까? 100% 활용하기!

인프런 비즈니스 서비스, 어떻게 쓰고 있는지 궁금하신가요 ? 인프런 비즈니스에서는 IT에 특화된 강의 및 실무 강의들로 약 3,100개 이상의 콘텐츠를 제공하고 있으며, 한달 평균 65개의 강의가 새롭게 업로드 되어 최신 강의를 빠르게 필요한 실무자들에게 제공할 수 있습니다. 또한, 이미 누적 수강생 1,200만명이 인프런을 통해 학습하고 있습니다.오늘은 다양한 인프런 비즈니스 기능 중, '대시보드' 기능을 설명드리고자 합니다.대시보드 기능이란?- 학습자분들이 수강한 학습 현황, 학습 시간, 강의 평점, 인기 카테고리 및 인기 스킬태그를 관리자가 확인하기 쉬운 형태인 '대시보드' 로 제공하며,이에 맞춰 학습 독려 메일 발송 및 강의관리까지 연계 가능한 관리자 편의형 기능입니다.인프런을 사용하시는 비즈니스 관리자분들에게 받았던 피드백 중 바로 '어떤 강의를 선호하며, 어떤 데이터를 통해 강의를 제공해야 만족할까? '에 대한 부분이 유독 많았는데요.사실 관리자 분들께서도 교육 서비스 제공 시, 학습자분들에게 실질적으로 도움이 되는 강의 주제를 맞추기 어려우실 수 있을 것 같습니다. 많은 인터뷰를 듣고 난 뒤, 인프런에서 관리자님들의 편의를 위해  “대시보드” 기능을 새로 출시하였습니다! 대시보드 내 인기 카테고리 , 인기 스킬태그 등을 통해 실제로 학습하는 학습자분들의 강의 유형을 알고, 수강평을 통해 강의 만족도를 파악하여 추후 해당 카테고리의 강의들을 보강할 수 있는 중요한 역할을 하고 있습니다.TIP 하나 더!대시보드를 보고 나서, 더 자세히 “우리회사가 어떤 강의를 가장 많이 듣고 얼마나 듣지?”를 알고 싶으시다면, 담당 영업매니저에게 살짝 ‘이용보고서’를 문의해보세요! 빠르고 정확하게, 해당 기간 내 이용보고서를 공유드립니다 :) 우리 모두, 교육에 진심이니까!임직원들이 만족하고, 실제 역량에 도움이 되는 교육을 위해, 인프런 비즈니스는 계속해서 노력하겠습니다. 

업무 자동화대시보드업무자동화비즈니스교육사내교육IT교육DX교육비즈니스교육실무강의강의

셰리

인프런 유저는 어떤 사람들일까? 직접 만나봤습니다 🏃

인프런 유저가 벌써 120만 명을 앞두고 있습니다! 👏인프런은 항상 여러분들이 어떤 사람인지, 인프런을 어떻게 이용하고 있는지 항상 궁금했는데요.그래서! 여러분의 서비스 경험을 책임지는 CX 파트에서 인프런을 가장 꾸준히, 활발하게 이용하시는 유저 186분을 만나봤습니다.오늘은 그중 재밌는 내용 몇 가지를 여러분께 소개해 드릴게요. 🙂간단 요약! 인프런 유저 특징 인프런에서 가장 만족하는 부분1) 강의 2) 기능 (질문 & 답변 게시판 / 로드맵)질문 & 답변 게시판은 강의 수강생 누구나 지식공유자에게 질문을 남길 수 있는 게시판입니다. 서비스 상단 [커뮤니티]를 통하거나, 수강 중인 강의 페이지의 [커뮤니티]를 통해 진입할 수 있어요. 강의 수강 중 이해가 안 되는 부분이 생겼을 때, 질문 & 답변 게시판을 적극적으로 활용해 보세요! 실제로 입문자 혹은 독학하시는 분들이 많은 도움을 받은 기능이라고 합니다.질문 & 답변 게시판 구경하기 >>로드맵은 다양한 강의를 엮어 이상적인 학습 순서를 제안하는 기능입니다. 지식공유자 혹은 인프런이 직접 제작하는 로드맵은 학습의 방향성을 확인할 수 있다는 점에서 인프러너분들의 만족도가 높았다고 해요. 로드맵에만 쓸 수 있는 할인 쿠폰도 있답니다!로드맵 구경하기 >>CX 파트에서는 인터뷰를 통해 유저분들의 생각을 직접 들어볼 수 있어 의미 있었다고 말씀해 주셨는데요.인프런을 꾸준히, 열심히 이용해 주신 유저분들을 직접 인터뷰한 CX 파트의 간단한 소감을 들어볼까요? 💌 인프런을 열심히 그리고 꾸준히 이용하시고 애정해 주시는 유저들을 직접 만나보니 서비스 운영자로서 뿌듯함을 많이 느꼈고, 앞으로 인프런을 더 성장시키는 방향성을 알 수 있는 계기가 되었어요.특히나 평소 CS 업무를 하다 보면 불편하거나 아쉬운 부분에 대한 VOC를 많이 듣는 편인데, 이번 유저 인터뷰에선 전반적으로 서비스를 매우 만족스러워하시는 분들의 의견을 듣다 보니 서비스에 대한 중립적인 시야를 가질 수 있게 되었고요.또, 유저분들과 직접 많은 이야기들을 나누다 보니 콘텐츠, 기능 개선 등 인프런이 앞으로 나아가야 할 방향에 대해 더 구체적으로 고민해 볼 수 있는 시간이 되었던 것 같습니다.유저분들이 저희 서비스에 보여주신 애정과 관심만큼 앞으로도 인프런에서 유익한 시간을 보내실 수 있도록 노력하겠습니다. 앞으로도 인프런 서비스에 많은 사랑 부탁드립니다. 감사합니다 🥰 앞으로도 인프런 기능이나 강의 관련해서 새로운 제안이나 요청 사항이 있으시다면 언제든 알려주세요. 인프런은 여러분의 소중한 의견을 귀 기울여 듣겠습니다.인프런에 바란다 >>

인프런인프러너유저강의지식공유자로드맵질문답변

[강의 정리][1편] 스프링 DB 1편 데이터 접근 핵심 원리 by 김영한

JDBC자바의 데이터베이스 표준 인터페이스DB 벤더 회사의 jdbc 드라이버와 함께 어떤 DBMS와도 사용 가능Sql MapperjdbcTemplate, Mybatis : sql 전달, 응답을 객체로 편리하게 변환 가능, jdbc의 반복 코드 제거ORM코드만으로 가능하다객체지향적으로 작성할 수 있으며, 동적인 쿼리의 재활용에 있어서 편리하다.Sql mapper에 비해 DB에 종속적이지 않다.JPA같은 경우 기본적인 쿼리는 자동 생성해주기 때문에 반복적인 sql 제거가 가능하다.  * 내부에서는 모두 Jdbc 기반이다.  DriverManagerJdbc 연결 관리각각의 DB드라이버들에게 url 정보를 체크해서 처리할 수 있는지 확인한다. Connection Pool필요한 만큼 커넥션을 미리 확보 & 커넥션 재사용대표적인 커넥션 풀 - HikariCPDataSource 인터페이스를 통해 커넥션풀에 직접 접근 없이 커넥션 획득이 가능하다DriverManager에서 DataSource를 사용하려면 DriverManagerDataSource라는 구현 클래스를 통해야한다.DriverManager은 커넥션을 위한 정보 파라미터 계속 넘겨줘야하고, DataSource는 getConnection로 커넥션을 가져온다.설정과 사용의 분리   트랜잭션데이터 베이스 작업단위원자성(all or nothing)모든 작업이 성공했을 때 commit하고, 하나라도 실패한다면 rollback-> 데이터의 일관성 유지 가능 격리성격리수준READ UNCOMMITEDREAD COMMITTED 기본REPEATABLE READ - update x, insert oSERIALIZABLE - select for updateDB 세션이 트랜잭션을 관리자동 커밋은 각각의 쿼리가 트랜잭션 단위이기 때문에 원하는 단위로 묶을 수 없다트랜잭션의 범위를 지정한다는 것은 수동 커밋을 의미한다  

데이터베이스강의

자바 ORM 표준 JPA 프로그래밍 기본편(김영한) 2

프로젝션 JPQL의 경우 패키지명까지 참조해서 생성자 매핑시켜주기 ex) select com.myproject.myapp.dto.MemberDto(m.id, m.name) ... result class 또한 DTO로 매핑 querydsl의 경우 @QueryProjection을 통해 querydsl용 생성자를 만들어서 매핑 가능 이 경우, select절에 Projections.constructor(Dto.class, ...)로 매핑 Projections.bean과 Projections.field에 비해 연쇄 수정이 적어지는 장점이 있음   페치 조인 페치 조인은 연관관계 엔티티까지 한 번에 불러오는 것 일대일 연관관계에서 주로 사용 일대다 컬렉션 페치 조인은 한 번만 지원되며,사용 시 중복조회가 일어나기 때문에 select distinct 사용하여야함 @BatchSize() 또는 hibernate.default_batch_fetch_size 사용해서 fetch join으로 불러올 엔티티 개수 지정 가능 원하는 필드만 조회하거나 연관관계가 조건절에만 필요한 경우 fetch join이 아닌 일반 조인 사용할 것.   다형성 쿼리 상속 연관관계 SINGLE_TABLE 타입의 경우type(i) in (Book, Movie) 또는 treat(i as Book).author = 'kim' 으로 사용   정적 재사용 쿼리 @SqlResultSetMapping(name = "NamedQuery 이름",entities = {    @EntityResult(entityClass=Member.class,                                   fields = { @FieldResult(name = 'id', column = 'order_id') }                                   column이 실제 테이블 컬럼명, 타입도 지정 가능                                  )           },columns = {@ColumnResult(name = 'item_name')}이것을 통해 projection 필드(가공한 필드)를 가져올 수 있음) NamedQuery 재사용 가능한 정적 쿼리 - 파라미터도 전달 가능 어플리케이션 로딩 시점에 쿼리 검증함 @Query도 NamedQuery @NamedQuery(name = "", query = "") NamedNativeQuery name, query, resultClass   벌크연산 변경감지만으로 데이터 수정하면 업데이트 쿼리가 남발됨. update, delete, insert select 사용 execute하면 영향받은 엔티티수 반환 벌크 연산은 DB에 직접 쿼리 날리기 때문에 영속성 컨텍스트 초기화가 필요@Modifying 또는 em.flush(); em.clear();        

JPA강의김영한프로젝션페치조인다형성쿼리Named쿼리벌크연산

<강의정리> 따라하며 배우는 도커와 CI환경(John Ahn) 1 - 도커 개념 정리

도커의 장점 일반적으로 프로그램을 다운받을 경우, installer 또는 io(file archive) 이용 -> 에러가 날 수 있음도커를 이용한 프로그램 다운 docker run -it redis -> 간편   도커 개념 서버에서의 컨네이터는 spring, mysql, react, redis ... 도커 이미지 - 프로그램을 실행하는데 필요한 설정과 종속성을 갖고있음 도커 컨테이너 - 이미지의 인스턴스이며, 프로그램을 실행함다양한 프로그램을 컨테이너로 추상화함으로써 어떠한 클라우드에서도 동일한 인터페이스 제공   도커 흐름 도커 클라이언트 -> 도커 서버 -> 캐시에서 이미지 반환 또는 도커허브에서 이미지 반환 기존의 가상화 기술은, 하이퍼 바이저를 통해 다수의 게스트 OS를 구동하고 호스트 OS와 하드웨어를 게스트 OS에 에뮬레이트(복제)함 도커 컨네이터 가상화 기술은 하이퍼바이저와 게스트 OS없이 호스트 OS위에서 커널을 공유하여 애플리케이션 실행 패키지(이미지)를 배포만 하면 됨. 경량화독립적인 컨테이너이기 때문에 하드 디스크 상에서도 격리됨; 프로세스를 작동시키는데 필요한 리소스 포함 도커 작동 원리; 리눅스 기능 1. Cgroup(control groups) - 시스템 리소스 사용량을 관리, 어플리케이션 cpu memory 제한 가능 2. 네임스페이스 - 프로세스를 격리시킬 수 있는 가상화 기술 프로그램 -> 리눅스 커널(이미지의 파일 스냅샷 전달) -> 리눅스 VM -> OS -> 하드웨어(프로그램 실행)

docker강의docker-container

spring mvc 2(김영한) - 메세지 소스, 검증, 로그인

메세지 소스 변수 사용 가능, 점(.)으로 이름 나눌 수 있음, default message 제공 가능   검증 bindingResult -> addError() rejectValue() FieldError() 파라미터 값도 재전달 가능 bindingResult에서도 message source 사용 가능 spring.messages.basename=messages,errors -> errors.properties에서 사용 단계적 메세지 처리 가능; 자세한 값, 기본값 순으로 메세지 반환함 code.objectName.fieldName 식으로 값 설정하면됨(typeMismatch.user.age) implements Validator, @Override supports(Class<?> clazz), @Override validate(Object target, Error errors)   검증2 - Bean Validation @NotNull @Email @Range(min, max) @Max(int) @Pattern(regexp="") 상황에 따라 제한조건이 다름 - null 또는 not null 이 경우, @NotNull(groups={SaveCheck.class, UpdateCheck.class}) 설정 후 @Validated(SaveCheck.class)로 사용하거나 dto 분리해서 사용하면 됨   로그인 처리 Cookie or @CookieValue() 초기화 시 null값 넣어주고, setMaxAge(0) (public static final == interface 일반 변수) HttpSession or @SessionAttribute   필터 웹과 관련된 공통관심사는 AOP보다 서블릿 필터 또는 인터셉터로 해결하기 Http 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러 Filter 사용하고 chain.doFilter()해줘야 그 다음 단계로 넘어감 FilterRegistrationBean<Filter> setFilter() addUrlPatterns() 필터 조건에 맞지 않는 경우 거를 때는 sendRedirect로.   인터셉터 서블릿 필터는 서블릿이 제공, 스프링 인터셉터는 스프링이 제공 Http 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러 HandlerInterceptor, preHandle(), postHandle(), afterCompletion() ModelAndView 사용 가능 webConfig 빈등록 addInterceptors(InterceptorRegistry registry) registry.addInterceptor(new CustomInterceptor()) addPathPatterns("/**"), excludePathPatterns("/css/**", )    

강의김영한

spring mvc 2(김영한) - 오류처리, 컨버터, 파일업로드

오류 처리 response.sendError(404, "404오류"); extends webServerFactoryCustomizer<ConfigurableWebServerFactory> void customize(ConfigurableWebServerFactory factory) new ErrorPage(status, path) factory.addErrorPages(); 예외 발생 시 WAS로 다시 가서 필터 서블릿 인터셉터 컨트롤러를 재호출한다.   필터 오류 처리 add DispatcherType ERROR(서버 오류 반환) 기본값은 REQUEST(클라이언트 요청)   인터셉터 오류 처리 excludePathPatterns 에서 error 페이지 경로 추가하기 오류 발생 시, after completion 호출 후 에러 페이지로 이동 추가 안 한다면, post handler 거쳐서 이동   스프링 기본 에러 반환 return /error 경로 지정하면 생성한 오류 페이지 자동 반환 우선순위는 templates(500, 5xx) -> static(500, 5xx) -> templates(error.html) API 에러 처리는 @RequestMapping에 produces = MediaType JSON으로 처리 BasicErrorController에서는 produces 자동 구현되어있음(/error)   HandlerExceptionResolver implements HandlerExceptionResolver e instanceOf IllegalArgumentException response.sendError() || ModelAndView || response.getWriter().println() 으로 json 반환도 가능 WAS로 안 돌아감 webConfig에 extendHandlerExceptionResolvers 빈등록 필요 CustomException에 @ResponseStatus 지정 가능, reason 속성으로 메세지 지정도 가능return ResponseStatusException으로 바로 반환도 가능   컨버터 implements Converter interface <S, T> @Override convert() ConversionService DefaultConversionService, addConverter(new CustomConverter()) 등록 후, 서비스 의존주입 받아서 service.convert("10", Integer.class) 뷰 템플릿에서는 ${{}} 또는 th:field로 컨버터 적용가능   Formatter Formatter<Number> 확장받아서 parse 메서드 오버라이드; NumberFormat.getInstance(locale), return numberFormat.parse(변수) print 메서드 오버라이드 numberFormat.format(변수) 컨버전 서비스 등록 시 포맷터 추가 가능(addFormatter()) 스프링 기본 제공 포맷터@NumberFormat, @DateTimeFormat 등   파일 파일업로드 multipart/form-data 타입으로 전송 서로 다른 타입의 테이터를 한 번의 form 전송으로 전송 spring.servlet.multipart.max-file-size 또는 max-request-size 설정 file.dir 파일 저장할 주소를 변수로 생성한 후 @Value("${file.dir}")로 가져와서 사용가능 MultiPart(Part) -> part.write(fileDir + part.getSubmittedFileName()); MultipartFile로 파라미터 받고 !file.isEmpty()일 경우에 file.transferTo(new File(fileDir + file.getOriginalFilename())) 업로드 시 확장자 분리 -> originalFilename.substring(lastIndexOf(".") + 1)새로운 파일 저장경로에 확장자 붙여서 저장   첨부 이미지 다운로드 @ResponseBody로 반환 + return new UrlResource("file:" + file~.getFullPath(filename)); 첨부파일 다운로드 시에는 UrlResource + header 설정해주고 넘겨줘야함 contentDisponsition = "attachment; filename=\"" + UriUtils.encode(uploadFileName, StandardCharsets.UTF-8) + "\"" .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)    

김영한강의

스프링 핵심원리 기본편(김영한) 1 - 객체지향 DIP와 스프링 DI, IoC

  객체는 객체와 끊임없이 상호작용한다. 그렇기에 유연한 변경이 가능해야한다. 예를 들어, 자동차라는 상위 클래스를 다양한 자동차 브랜드로 구현될 수 있고, 운전자가 변화해도 자동차는 영향을 받지 않는다. 사용자, 주문, 할인 등 여러 독립적인 특징을 가진 기능은 클래스로 분리하여 각 클래스에서만 수정 및 사용한다.   역할과 구현을 분리 - 인터페이스와 콘크리트 클래스 인터페이스는 안정적이게, 확장이 무한대로 가능하게 설계해야한다.   SOLID 객체지향 설계 원칙 1. SRP 단일책임원칙 - 변경이 용이한 단위적 책임인가2. OCP 개방폐쇄원칙 - 코드의 변경 없이 확장이 가능한가(조립만으로 변경)3. LSP 리스코프 치환 원칙 - 하위 클래스는 인터페이스(상위 클래스)를 위반하지 않아야한다4. ISP 인터페이스 분리 원칙 - 여러 개의 인터페이스를 통해 명확한 기능을 갖고 있고, 대체 가능성이 높은 환경을 구현할 것5. DIP 의존관계 역전 원칙 - 추상화에 의존할 것, 인터페이스(역할)가 중심이 되어야한다. 구현체에 의존하면 다형성을 잃는다(재활용성을 잃는다) 스프링 컨테이너에 객체 지향 적용 객체를 생성하는 역할과 객체를 실행하는 역할을 분리.의존은 인터페이스로 하고, 설정 파일을 통해 구체적인 구현체를 의존 주입구현체 변경 시 설정 파일만 변경하면 된다.(조립)=> 제어의 역전; 어떤 구현체를 사용할 것인지 AppConfig(Spring)가 결정한다. 동적인 인스턴스 의존관계    

객체지향javaSOLIDspringDIIoCDIP강의김영한

객체 지향 프로그래밍 입문(최범균) 2 - 다형성, 추상화, 조립

  다형성이란, 여러 모형으로 변화하는 것이다. 하나의 객체가 여러 타입을 갖는 것이다. 추상화란, 특정한 성질(interface) 또는 공통 성질(abstract, 일반화)을 뽑아내는 과정이다. 추상화를 통해 객체는 다형적인 모형을 변화 가능하다   <추상화 시점> 추상화는 의존 대상이 변경하는 시점에 추가한다. 실제 변경 및 확장이 일어날 때 공통점을 파악하고 뽑아낸다.   <추상화 예시> 클라우드 파일 관리 기능이 있고, 대상 클라우드의 종류가 n가지일 경우.클라우드 종류에 따라 if문으로 분기하는 로직이 아닌 공통기능인 클라우드 파일 시스템을 추상화한다.클라우드 파일 시스템에서는 파일 목록과 관련된 CRUD 기능을 추상화하고,클라우드 파일에서는 개별 파일의 CRUD 기능을 추상화한다. 특정 클라우드 구현체에서는 추상 클래스를 상속받아서 기능을 재정의한다. 추상화가 진행되면, 구현 클래스의 변경은 있더라도(조립) 서비스 로직은 바뀌지 않는다.   <상속보다는 조립> 상속을 통해서 재사용을 하게 된다면,1. 상위 클래스의 변경이 어렵고2. 기능과 확장이 필요한 만큼 클래스가 증가하고3. 상속을 오용하게 된다.(비슷한 메서드 착오) 상속은 하위타입일 경우에 진행하고, 보통의 경우 객체를 참조하는 방식으로 진행할 것.    

java객체지향최범균강의추상화다형성조립객체참조

객체 지향 프로그래밍 입문(최범균) 3 - 분리, 의존 주입, DIP

<역할과 기능 분리 방법> 1. 패턴 적용 전형적 분리(아키텍처, 디자인패턴) 2. 계산 분리 로직의 기능화 3. 연동 분리 클래스 분리 4. 연속적인 if-else는 추상화 고민할 것   적절한 역할 분리는 테스트도 용이하게 한다.사용자와 직접적으로 관련된 기능은 내부 메서드로, 간접적으로 관련있는 기능은 별도의 클래스로 분리한다.   <의존> 순환 의존은 변경이 연쇄적으로 전파된다. 기능 변경의 파장이 커지면 안 좋기 때문에 의존은 적을수록 좋다. 의존대상의 기능이 많은 경우 클래스로 분리하거나 단일 기능으로 묶을 수 있는지 확인하라. 예를 들어 민원팩토리, 민원리포지토리를 민원등록으로 묶기   <스프링 의존 주입> 추상적 인터페이스를 의존하고, 의존 주입은 보통 생성자 방식으로 외부(스프링)에서 진행한다. 내부에서 new()로 생성하는 것과 반대이다. 1. 의존 대상이 바뀌면 그 대상을 조립하는 부분만 수정하면 됨 2. 대역 객체를 통해 테스트가 가능하다   <DIP 의존 역전 원칙> 고수준 모듈(기대수준), 저수준 모듈(단위적 실제 행위) 고수준 모듈을 의존해야한다. 반대로 고수준 모듈이 저수준 모듈을 의존하는 경우, 저수준 모듈이 변화할 때 고수준 모듈에 영향을 끼침 (목표를 향해 개발하는 것이 아닌, 개발에 따라 목표가 변하는 현상)고수준 모듈을 구현한 추상타입(인터페이스)을 저수준 모듈이 의존하는 방식을 추구해야한다.    

java강의최범균DIPDIinterface분리객체지향

객체 지향 프로그래밍 입문(최범균) 1 - 객체지향, 캡슐화

좋은 코드란, 낮은 비용으로 변화할 수 있는 코드이다 이것은 1. 캡슐화 2. 추상화(다형성 지향)로 이루어낼 수 있다.   절차지향적 코드는 진행될수록 여러 조건문으로 복잡해질 수 있다. 객체지향적 코드는 객체가 제공하는 기능(메서드)이 중심이 되어 설계하는 것이다.  - 호출, 리턴, 익셉션 등의 메세지의 교환 - 데이터 클래스(VO, DTO)는 객체가 아니다. 객체의 기능이 없이 값에만 접근하기 때문이다.   캡슐화는 데이터와 관련된 기능을 묶는 것이다. 데이터의 상세 내용을 외부에 감추고, 외부와 무관하게 객체 내부의 구현 변경이 가능하다. 객체의 기능을 비즈니스 로직이 아닌 객체 내부의 메서드로 구현하면, 기능에 변화가 요구될 때 해당하는 내부 기능을 변경하면 캡슐화를 사용한 곳에 별도의 수정이 필요하지 않다.   캡슐화의 규칙 1. 데이터를 요구하는 것이 아닌 데이터의 처리를 요구할 것if(member.getAge() > 19) Xif(member.isAdult()) O 2. 메서드에서 생성한 객체의 메서드만 호출할 것파라미터로 받은 객체의 메서드만 호출할 것필드로 참조하는 객체의 메서드만 호출할 것 >> 연속적인 메서드 호출이 아닌 객체에 있는 하나의 메서드로 처리member.isAdult() + member.isVIP() + member.addCoupon()으로 하나씩 처리하는 것보다member.receiveBenefits()로 위 세 개 기능 묶기     객체는 속성과 기능으로 구성되어있다. 객체의 여러 기능을 참조하고 묶어서 새로운 기능에 사용하는 것은 객체 지향적인 방식이다.        

java객체지향최범균강의캡슐화DDD

<강의>따라하며 배우는 도커와 CI환경(John Ahn) 4 - 운영환경(aws eb)

전체 프로세스로컬 -> 깃허브 -> travis CI -> AWS EB (AWS S3 -> AWS ECS(ec2 컨테이너 인스턴스)에서 도커 이미지 생성 및 배포   운영환경을 위한 Nginx- 리액트 컨테이너 안의 서버로 엔진엑스 사용- 빌드 파일에서 엔진엑스가 정적파일 찾아서 반환- Dockerfile에 Nginx 이미지 추가 FROM nginx COPY --from=builder /usr/src/app/build /usr/share/nginx/html builder 스테이지에서 생성된 빌드 파일을 엔진엑스가 사용하는 경로(기본값)에 복사함   .travis.yml 파일 sudo: required language: generic services: - docker before_install: - docker build -t <이미지 이름> -f <dockerfile명> script: - docker run -e CI=true <이미지 이름> docker run test -- --coverage after_success:   AWS- ec2는 컴퓨터를 임대하는 개념 -> OS, 웹서버, DB 설치해서 사용- EB(Elastic Beastalk) -> 서버, 언어, 도커와 함께 개발된 서비스를 배포 및 확장- eb는 ec2, db, 보안 그룹 등을 컨트롤함(eb가 더 넓은 개념)   AWS EB 환경구성브라우저 -> eb의 로드발란서가 ec2 인스턴스들에게 분산시   .travis.yml 파일에 배포 설정 추가script 아래에 deploy: provider: elasticbeanstalk region: ap-northeast-2 app: docker-react-project <앱이름> env: DockerReactProject-env <환경이름> bucket_name: elasticbeanstalk-ap-northeast-2-234234235 <자동 생성 s3 버킷 이름> bucket_path: docker-react-project <앱이름> on: branch: master <git 배포용 branch 선택>   travis ci에 aws 접근 권한 설정아이엠 사용자 생성 후 트래비스 환경 변수에 AWS_ACCESS_KEY, AWS_SECRET_KEY 값 추가.travis.yml 파일에도 deploy 부분에access_key_id: $AWS_ACCESS_KEYsecret_access_key: $AWS_SECRET_KEY   엔진엑스 포트 매핑Dockerfile에 FROM nginx 밑에 EXPOSE 80 추가

docker강의docker-nginxdocker-travisdocker-awsdocker-aws-eb

<강의 정리>따라하며 배우는 도커와 CI 환경(John Ahn) 2 - 도커 명령어

1. 기본적인 도커 클라이언트 명령어 이미지 생성  docker create <이미지 이름> docker build -> Dockerfile을 이용하여 이미지 생성 docker build -t <이미지 이름 지정>   이미지 실행 docker run <이미지 이름> <명령어> docker run hello-docker ls docker run -p <포트지정> docker run -f <dockerfile 지정> docker run은 아래와 같음  docker create <이미지 이름> + docker start <컨테이너 아이디 또는 이름>   컨테이너에 명령어 전달 -> 컨테이너란 이미지를 실행한 상태를 일컬음 docker exec <이미지 아이디> <명령어> 레디스를 이용한 예시 docker run redis -> 레디스 서버 실행 docker exec -it <컨테이너 아이디> redis-cli -> 레디스 클라이언트 실행 -it 는 interactive와 terminal 옵션 -> 계속해서 명령어 적용 유지시켜줌 docker exec -it <컨테이너 아이디> sh 해서 터미널 환경에 들어가서 명령어 사용하면 간단 나올 때는 ctrl + D    이미지 중지 docker stop <이미지 이름> -> 실행중이던 것 완료하고 중지 docker kill <이미지 이름> -> 바로 중지   컨테이너 확인 docker ps -> 실행 중인 컨테이너 docker ps -a   (중지된) 컨테이너 삭제 docker rm <이미지 이름> -> 중지된 컨테이너 삭제 docker rm 'docker ps -a -q' -> 모든 중지된 컨테이너 삭제  docker rmi <이미지 아이디> docker system prune -> 중지된 컨테이너, 이미지, 네트워크 모두 삭제    도커 컴포즈 명령어 - docker compose yml 설정 파일 필요? docker-compose up docker-compose down   2. Dockerfile 생성하기 dockerfile 설정파일을 통해 도커 서버가 이미지를 생성함 도커 이미지가 필요한 것; 이미지는 여러 레이어로 구성 베이스 이미지 -> 이미지의 기반(OS) 파일 스냅샷 -> 필요한 파일을 다운로드할 명령어 시작 시 실행될 명령어   Dockerfile 예시 # 베이스 이미지FROM <이미지 이름>:<태그> -> 태그 안 붙이면 자동으로 최신 버전예시 FROM node:10 # 파일 다운로드RUN <명령어>예시 RUN npm install # 컨테이너 시작 시 실행될 명령어(1회 한정)CMD ["node", "server.js"]   Dockerfile로 이미지 생성하기 docker build ./ 또는 docker build . docker build -t <자신의 도커 아이디> <저장소;프로젝트 이름> : <버전> ./ docker build -t example1234/hello:latest ./ -> docker run -it example1234/hello 로 실행 가능   파일 못 찾는 현상 -> 파일 스냅샷 안에 넣어줘야 참고하는 파일이 컨테이너 안에 생성됨 FROM 다음에 COPY ./ ./ 로 복사    이미지 실행 시 포트 매핑 docker run -p <브라우저에서 사용할 포트>:<컨테이너 포트> <이미지 이름>   working directory 명시해주기 FROM 다음에 WORKDIR /usr/src/app -> 이미지 안에서 어플리케이션 소스를 갖고 있을 디렉터리를 생성 root에 접근하려면 쉘에 들어가서 cd로 이동하면 됨   어플리케이션 소스 변경 시 효율적으로 재빌드하기 COPY package.json ./  RUN npm install COPY ./ ./ -> 디팬던시가 변경되지 않는 한 디팬던시를 항상 받지 않고 캐시된 것을 이용하기 때문에 소스 변경 반영이 효율적이게 된다.     3. Docker Volumn  COPY 대신 Docker Volumn을 이용해 로컬 파일을 참조 docker run -p ... -v 호스트경로:참조할 도커 디렉터리 지정예시) docker run -v /usr/src/app/node_modules -v $(pwd):/usr/src/app 참조하지 않을 디렉터리는 호스트 경로($pwd)없이 경로 지정하면 참조 안 하고 컨테이너 내에서 찾아서 사용함$(pwd)는 현재 디렉터리; print working directory -> 빌드 없이 stop과 run으로 소스코드 반영이 가능하다.

docker강의docker명령어Dockerfile

실전 Querydsl(김영한) 1

문법countcolumn.sum avg max minjoin, orderBy, groupBy, having()eq, in, isNull(), between, goefetch, fetchOne, fetchResults   조인join(innerJoin), leftJoin, fetchJoin연관관계 없는 세타 조인 from(member, team) - on절 사용 가능(leftJoin)세타조인이 leftJoin으로 진행될 경우 별칭사용 안 함   서브쿼리JPAExpressions - static으로 사용하기QMember memberSub = QMember("memberSub")로 엔티티 별칭으로 참조   기타assertThat(result).extracting("age").containsExactly(30)case문 - when().then().otherwise()   프로젝션 Projections.bean(MemberDto.class, member.username, member.age)setter 필요 Projections.field() Projections.constructor() DTO 생성자에 @QueryProjection 붙여서 사용as 또는 ExpressionUtils로 dto 필드명에 맞추기   동적쿼리BooleanBuilder builder if(arg != null)builder.add(member.username.eq(arg)) jpaQueryFactory.selectForm().where(builder) where 다중 파라미터 -> 쿼리 조립 재활용 가능- Predicate, BooleanExpressions 사용- null 주의   기타update().set(member.age, member.age.add(1))delete().where(member.age.gt(10))sql function 사용 가능 ex) DATE_FORMAT   조회 API 컨트롤러jpaRepository.search(condition)MemberSearchCondition dto @QueryProjections쿼리스트링만으로 쿼리 자동생성 & 검색 기능 사용 가능   사용자 정의 리포지토리CustomRepositoryImpl interface CustomRepository extends MemberRepository(== JpaRepository)   페이징offset limit fetchResults => list, count 쿼리 호출 -> deprecatedfetchCount와 fetch로 Page 생성 가능또는 직접 count 쿼리 + select list 쿼리 조합다음은 필요할 때만 count 쿼리 실행하는 코드PageableExecutionUtils.getPage(content, pageable, () -> countQuery.fetchCount())

김영한강의Querydsl

자바 ORM 표준 JPA 프로그래밍 기본편(김영한) 1

JPA 정리 엔티티 매니저 엔티티 매니저 팩토리에서 엔티티 매니저들을 생성하며, 각 엔티티 매니저는 DB 커넥션 풀을 사용힌다. 엔티티 매니저를 통해서 영속성 컨텍스트 생성한다. 영속 상태는 커밋상태가 아니다. 커밋은 트랜잭션 단위이다.   영속성 컨텍스트의 특징 1차 캐시 동일성 보장 쓰기 지연(persist, flush) 변경 감지 지연 로딩(lazy)   연관관계 연관관계 주인을 판단하는 기준 : 외래키를 갖고있는가 또는 등록수정이 빈번한가 연관관계 주인에서는 컬럼에 @JoinColumn으로 조인 시 어떤 컬럼을 사용할 것인지 명시 가능하다. 연관관계 주인이 아닌 쪽에서는 mappedBy로 양방향 연관관계를 설정해줄 수 있다.양방향은 DBMS상에서는 없는 개념이지만 엔티티에서는 참조가능하다. 다대다 연관관계는 중간 테이블을 사용힌디/ - 일대다 & 다대일 테이블로 연결 cascade와 orphanRemovel를 통해 자식객체를 제어할 수 있다.부모엔티티에 있는 연관관계에서 사용 (@OneToOne 또는 @OneToMany) 프록시 객체로 조회하고, 초기화 요청들어가면(호출되면) 실제 객체에서 정보 추출캐시에 있다면 바로 실제 객체를 반환한다.   값 타입과 임베디드 타입 임베디드 타입은 객체 속성을 응집시킬 뿐만 아니라 도메인 메서드를 통해 개별적 처리가 가능하다.(객체지향적) 임베디드 타입을 한 엔티티에서 두 번 참조할 때 DB에 필드가 다르게 들어가야한다.-> 이 경우, AttributeOverride 어노테이션으로 컬럼명을 설정해줄 수 있다. 임베디드 타입은 같은 객체를 사용했을 때 객체 수정 시 모든 값이 변경될 수 있다.여러 엔티티에 공유하지 말고 각각 생성해야하며 수정 시에도 생성자를 통해 통으로 변경해야한다. 되도록이면 엔티티화(연관관계)하는 것이 식별자를 통해 추적, 변경할 수 있기 때문에 편리하다.   상속관계 @Inheritance(strategy=InheritanceType.XXX) 전략 JOINED 부모 자식 모두 테이블이 생성되며, 부모와 자식테이블은 부모의 pk로 연관관계가 이어져있다.  자식 insert 시 부모까지 insert되며, 조인을 통해서 테이블을 관리한다. SINGLE_TABLE 부모 테이블만 생성되며 자식 엔티티는 부모 테이블에 컬럼으로 추가된다. 부모클래스에 @DiscriminatorColumn(name="DTYPE; 컬럼 이름") 자식클래스에 @DiscriminatorValue("컬럼의 값으로 사용할 이름") PER_CLASS 자식 엔티티들만 독립적인 테이블을 생성하며 모두 부모 엔티티의 컬럼을 가진다. 자식 테이블의 pk는 부모 테이블의 pk를 사용한다.    

강의김영한JPAEntityManager영속성연관관계임베디드타입상속관계

실전! 스프링 부트와 JPA 활용2 (API 개발과 성능 최적화) - 김영한

 Rest API를 구현하며 알아보는 Entity를 반환하는 6가지 방식   Version 1. 엔티티를 직접 노출하는 방식 [문제] 엔티티를 직접 반환하는 방식은 중요한 정보가 노출되거나 필요하지 않은 데이터까지 불러 데이터 구조가 비대해질 수 있다.연관관계까지 호출할 경우, 역시 모든 속성을 호출함으로 비대해진다. 양방향 연관관계일 경우 한 쪽 컬럼에 @JsonIgnore을 설정하여 무한루프가 빠지지 않도록 주의해야한다.연관관계는 fetch option을 lazy로 설정하면 호출되지 않기 때문에 lazy로 설정한 후 필요한 경우 속성을 호출하는 방식으로 사용하는 것을 권장한다.   Version 2. 엔티티를 DTO로 변환하는 방식 stream을 이용한 entity -> dto 변환stream에서 filter, map을 사용해 중간변환을 하고 find 또는 collection 형태로 최종 가공. 필요한 속성 중심으로 가공할 수 있어 깔끔하게 전달 가능 [문제] 여전히 쿼리 반환 시 모든 데이터가 호출되기 때문에 성능 상 문제가 생길 수 있음   Version 3. 엔티티를 DTO로 변환하며 페치 조인으로 최적화 jpql에서는 join fetchquerydsl에서는 join() 후 fetchJoin() fetch join을 이용하여 한 번의 쿼리로 연관관계까지 모두 호출하도록 한다.그렇게 되면 지연 호출(lazy)로 인한 추후 연관관계 호출 시 추가적인 쿼리 발생이 일어나지 않으며,크로스 조인(cross join)으로 인한 데이터 중복 호출도 발생하지 않는다. 일대다 컬렉션 조인이 있을 경우, select distinct로 데이터 뻥튀기를 해결해줘야 한다. [문제] fetch join의 경우, 일대다 컬렉션 연관관계는 한 개 밖에 사용하지 못한다는 한계가 있다. 또한 컬렉션 페치 조인을 사용하면 페이징이 불가능하다.모든 데이터를 읽어온 후, 메모리에서 페이징을 한다;;   Version 3.1. 컬렉션 페치 조인 - 페이징 한계 돌파 일대다 관계에서 페이징을 하려면 일(1)을 기준으로 해야되지만컬렉션 페치 조인을 사용할 경우 다(N)이 기준이 되어 row가 생성된다.-> 하이버네이트는 이러한 문제로 메모리에서 페이징을 진행한다; [해결] ToOne 연관관계는 fetch join으로 호출한다. ToMany 컬렉션 연관관계는 지연로딩으로 조회한다. 이 때, hibernate.default_batch_fetch_size 또는 @BatchSize을 이용한다.이 옵션은 컬렉션 또는 프록시 객체를 설정한 size만큼 in query로 조회한다.1 + N 쿼리에서 1 + 1 쿼리로 최적화 된다.   Version 4. JPA에서 DTO 직접 조회 특정 용도에 필요한 컬럼들로만 구성한 DTO로 조회 jpql의 경우, select new package명.dto.ResponseDto(컬럼...) 및 클래스 명시querydsl의 경우, select(QueryProjections.constructor(Dto.class, 컬럼...)) 역시, 컬렉션 조회 시 1+N 문제가 있고, 해당 문제는 아래에서 다룸   Version 5. JPA에서 DTO 직접 조회 - 컬렉션 조회 최적화 컬렉션 연관관계는 일(1)의 id 목록을 파라미터로 전달하여 in query 별도 조회stream groupingBy로 일(1)의 id로 다(N)를 묶은 후 -> 일(1)의 속성에 넣어줌 /** order과 order_item으로 살펴보는 예시 */ // 1. order 목록 호출 List<OrderQueryDto> result = findOrders(); // 2. order의 id 추출 List<Long> orderIds = result.stream() .map(o -> o.getOrderId()) .collect(Collectors.toList()); // 3. order id로 order item 호출 List<OrderItemQueryDto> orderItems = em.createQuery( "select new ...OrderItemQueryDto(컬럼...)" + " from OrderItem oi" + " join oi.item i" + // 참고로, fetch join은 엔티티 조회에서만 가능 " where oi.order.id in :orderIds", OrderItemQueryDto.class) .setParameter("orderIds", orderIds) .getResultList(); // 4. order item을 order id로 grouping Map<Long, List<OrderItemQueryDto>> orderItemMap = orderItems.stream() .collect(Collectors.groupingBy(orderItemQueryDto -> orderItemQueryDto.getOrderId())); // 5. order에 order item 매핑해주기 result.forEach(o -> o.setOrderItems(orderItemMap.get(o.getOrderId())));   Version 6. JPA에서 DTO로 직접 조회 - 플랫 데이터 최적화 쿼리 한 번으로 모든 데이터 조회 [문제] 조인으로 인해 중복 데이터 생성될 수 있다.쿼리에서는 데이터를 모두 호출하기 때문에 애플리케이션에서 추가 작업이 발생할 수 있다.페이징이 불가능하다.

강의김영한REST_APIDTO

채널톡 아이콘