전체 글 (356)

맥북 블로깅을 위한 초간단 그림그리기 앱 (맥북 버전 그림판)

안녕하세요, 오늘은 맥북용 그림그리기 앱을 소개해드리겠습니다.

포토샵이나 유사 앱들처럼 사용하기 어려운 그림그리기 앱이 아닌 정말 간단한 그림그리기 앱입니다.

윈도우에는 별도로 설치하지 않아도 기본적으로 딸려오는 그림그리기 프로그램이 있습니다.

바로 "그림판(영문으로는 paint)"이라는 프로그램 인데요

전 그 프로그램을 한번 살펴보고는 "누가 이런 프로그램으로 그림을 그리지?" 라고 생각했습니다.

그러다가 누군가가 그 프로그램을 사용하는 것을 보게되었는데요

그렇게 쉬울 수가 없더군요

그 후로는 저도 매우 애용했던 프로그램이 바로 그림판이었습니다.

 

그런데 맥북에서는 그런 프로그램을 찾기가 쉽지 않았습니다.

그런데 최근에 다시 찾아보니 페인트 X 라는 앱이 있더군요.

앱스토어에서 paintx 로 검색을 하시면 아래처럼 일반 버전과 프리미엄 버전이 나옵니다.

맥용 그림그리기 앱 페인트 X

저는 일반 버전을 무료로 다운로드 받았습니다.

실행시키면 UI 자체가 윈도우의 그림판과 매우 유사합니다.

페인트 X 실행화면

윈도우의 그림판과 한번 비교해볼까요?

와~ 어쩜 이렇게 비슷할까요~

생긴것 부터가 너무 비슷하고요, 기능역시 대부분 비슷합니다.

그림판을 자주 사용하셨던 분들은 쉽게 적응하실 수 있습니다.

 

하지만 역시 애플의 앱스토어에는 완전무료 앱이 거의 없죠.

무료로 다운받아서 사용은 가능하지만 그리는 그림의 크기가 일정 사이즈를 넘어가게되면 워터마크가 표시됩니다.

좀 더 정확히 말씀드리자면 가로 또는 세로 어느 한쪽이라도 200픽셀을 넘어가면 워터마크가 생깁니다.

즉, 1024 x 199 는 워터마크가 생기지 않지만 199 x 200 사이즈에서는 워터마크가 생깁니다.

페인트 X Lite 워터마크

 

해당 워터마크를 없애려면 우측 상단의 Remove Watermark & AD 를 클릭하여 일정 금액을 지불해야합니다.

페인트 X Lite 워터마크 & 광고 제거 팝업창

커피 한잔 값도 안되는 $5 를 결제하면 워터마크 및 광고를 제거할 수 있다고 합니다.

저는 그렇게 크게 그림을 그릴 일이 없어 일단 무료로 그냥 사용중입니다.

어떠신가요? 블로깅 용도로 자잘하게 그림을 그려야 할 때도 사용하면 좋을 것 같지 않나요?

 

이상으로 맥북용 그림그리기 앱 페인트X 포스팅을 마칩니다.

예외 처리/에러 핸들링 잘하기

안녕하세요, 오늘은 예외 처리(에러 핸들링)에 대해서 얘기를 해볼까 합니다.

책에 나오는 또는 인터넷 상에서 간략하게 설명하는 try-catch 예외처리에 대한 내용은 아니고 직접 개발을 진행하면서 경험한 내용을 토대로 이렇게 하면 좋겠다 싶은 내용들을 공유하는 차원에서 작성하는 글입니다. 혹여나 더 좋은 방법이나 공유해주시고 싶은 내용이 있다면 댓글도 환영합니다.

 

우선 자바 서적이나 구글링을 해서 찾는 에러 핸들링에 대한 내용들은 대부분 try-catch 문을 사용하면 된다에서 끝납니다.

하지만 단순히 그렇게 작업하면 에러 핸들링을 잘한다고 할 수 없습니다.

복잡한 비즈니스 로직을 구현하다보면 여러 클래스들을 넘나들면서 여러 메서드들을 거치게 됩니다.

비즈니스 로직의 메서드 콜 체인

위 메서드 콜 체인은 하나의 프로세스(기능)이 처리될 때의 과정의 일부입니다. 예를들면 controller -> service -> dao 뭐 이런 건데요, 아무리 복잡한 서비스라 하더라도 결국 요청에서부터 응답을 주기까지의 과정은 결국 위 그림처럼 한 줄로 연결이 가능하죠.

 

따라서 어느 시점에 try-catch 로 예외를 잡아야 할지 그리고 예외를 잡았다면 어떤 처리를 해야할지에 대해 많은 고민이 필요합니다. 하지만 실상 그런 고민을 별로 하지 않고 대충 에러로그 찍어놓는 개발자들이 많은 것이 사실이죠. 심지어 catch로 잡아서 로그만 남기고 다시 throw를 하기도 합니다. 무슨 폭탄돌리기 하는 것도 아니고 말이죠 -_-;; 최악입니다. 그렇게 개발된 서비스는 하나의 에러에 대해서 중복적으로 여러개의 로그를 남기는 것을 확인할 수 있고 디버깅도 비효율적으로 만들어 버립니다. 그럼 어떻게 하는 것이 좋은 것일까요?

 

첫 번째는 요청을 받아서 응답을 주기까지 하나의 체인에서 특정 예외에 대한 처리는 한 곳에서만 하도록 하는 것이 좋습니다. 즉, 위 체인 그림에서 메서드 1, 2, 3, 4 중 하나의 메서드에서만 A라는 예외에 대한 처리를 한다는거죠. 컨트롤러 부분에서 ExceptionHandler를 이용해서 처리할 수도 있고 서비스 레이어에서 try-catch 문을 이용해서 처리해도 됩니다. 메서드3에서 예외처리를 해놓았는데 불필요하게 메서드2에서 또 하고 있다거나 한다면 둘 중 하나를 제거하는게 유지보수하는데 큰 도움이 됩니다.

 

두 번째는 catch를 했으면 적절한 처리를 해줘야 한다는 겁니다. 여기서 "적절한" 이라는 말이 애매할 수 있는데요, 단순히 로그만 찍고 catch한 예외를 다시 throw 하고있다면 해당 try-catch문은 쓸모없는 것일 가능성이 99.9%입니다. 그런 경우라면 callee에서만 catch해서 로그를 남기고 적절한 처리를 하는게 더 깔끔합니다. 굳이 두 군데서 나누어 catch할 필요가 없다는 거죠. 또, 어떤 예외는 잡아서 로그만 남기고 무시할 수도 있고 (옵셔널 값이 없다거나 하는 경우), 어떤 예외는 디버깅용 로그를 남기고 비즈니스 로직을 롤백하는 처리를 해야할 수도 있습니다. 해당 프로세스를 진행함에 있어서 어떻게 처리해야 할 지는 API스펙 또는 정책에 따라 달라질 겁니다.

 

마지막으로, 예외가 발생했을 경우 디버깅용 로그를 남기기로 했다면 정말 필요한 정보만 가독성 있게 로그를 남기는 것입니다. 한 줄의 로그만 잘 남겨도 디버깅하기 쉽도록 작성하면 유지보수 효율성 뿐만 아니라 로깅 시스템에 부하도 줄일 수 있게 됩니다.

 

제 경험을 토대로 신입 또는 주니어 개발자 분들께서 이렇게 해보시면 좋겠다는 생각으로 끄적여 보았습니다.

 

혹시 이 글을 읽으시다가 멋진 노하우가 있으신 분들은 댓글로 공유 부탁드려요 ^-^

 

[자료구조] AVL 트리 특징 및 로테이션 기준 에서 AVL 트리의 특징 및 회전의 4가지 케이스들에 대해서 말씀드렸었는데요

로테이션을 어떻게 하는지에 대해서 좀 더 자세히 설명드리기 위해 추가 포스팅을 합니다.

AVL 트리 회전

우선 첫 번째 케이스였던 Left-Left 케이스에 대해서 살펴보도록 하겠습니다.

Left-Left 케이스는 부모노드와 pivot 노드가 모두 left heavy 인 케이스였죠. 

즉, 신규로 어떤 노드를 추가했을 때 해당 노드의 부모노드와 pivot 노드의 균형값(balance factor)이 (-)라는 얘기죠.

아래 예제를 한번 살펴보겠습니다.

 

figure1. Left-Left 에제 base tree

이런 트리가 있다고 가정해봅니다. 각 노드의 바깥쪽에 씌여진 숫자가 균형값(balance factor)입니다.

자, 이제 이 트리에 2번 노드를 추가합니다.

figure2. Left-Left 예제 신규노드 추가

새롭게 노드가 추가가 되면서 5번 노드와 10번 노드의 균형값에 변동이 생기고 10번 노드의 균형값이 -2가 되면서 AVL 트리의 조건에 위배됩니다. 따라서 회전을 해야하는데 이렇게 새로 추가한 노드의 부모 노드(5번 노드)와 pivot 노드(10번 노드)가 모두 left heavy인 경우에는 오른쪽으로 1회전을 해주면 됩니다.

figure3. Left-Left 예제 로테이션하기

오른쪽으로 회전을 해주게되면 모든 노드의 균형값이 0이 되면서 AVL 트리의 조건을 만족하게 됩니다.

 

두 번째로 왼쪽으로 회전해야하는 Right-Right 케이스를 살펴보겠습니다.

Right-Right 케이스는 부모노드와 pivot 노드가 모두 right heavy 인 케이스이죠.

아래 그림은 right-right 케이스에 대해서 왼쪽으로 회전하는 예제입니다.

figure4. AVL 트리의 왼쪽 회전 (출처: tutorialspoint.com)

최초에 A, B 노드가 있었고 C 노드가 추가가 되면서 right unbalanced 트리가 됩니다.

right-right 케이스에 해당하므로 왼쪽으로 1회전 해주면 balanced 트리가 되죠.

 

이제 세 번째 케이스인 left-right 케이스를 한번 보겠습니다.

left-right 케이스는 부모 노드와 pivot 노드의 균형값이 각각 right-heavy, left-heavy, 인 경우라고 했었습니다.

이 말은 결국 어떤 노드의 왼쪽 서브트리의 오른쪽 서브트리에 새로운 노드를 추가하게 되는 경우라고 이해하셔도 됩니다.

아래 그림을 보시면 이해가 더 빠를 겁니다.

figure5. left-right 케이스

최초에 C와 A 노드만 있었는데 B 노드가 추가가 됩니다. 그러면 A노드의 오른쪽 서브트리로 들어가게 되겠죠. 위 그림처럼 말이죠.

figure6. 왼쪽 회전

left-right 케이스라면 왼쪽으로 회전했다가 오른쪽으로 회전하면 되므로 위 그림처럼 왼쪽으로 회전을 시켜봅니다.

figure7. 왼쪽 회전의 결과

왼쪽으로 회전을 시키면 위 그림 처럼 됩니다. 어랏? 어디서 본 그림이네요?

맞습니다. 제일 처음에 봤던 left-left 케이스와 똑같아 졌습니다.

left-left 케이스면 오른쪽으로 회전을 시켜야겠죠?

figure8. 오른쪽 회전
figure9. 균형 트리

이제 균형이 모두 맞춰졌습니다.

 

마지막으로 right-left 케이스를 살펴보겠습니다.

간단하게는 left-right 케이스와 반대라고 생각하시면 됩니다. 

left-right 케이스가 왼쪽 서브트리의 오른쪽 서브트리에 새로운 노드가 추가되는 케이스였으니,

right-left 케이스는 오른쪽 서브트리의 왼쪽 서브트리에 새로운 노드가 추가되는 케이스겠죠.

그리고 그 말은 부모 노드와 pivot 노드의 균형값이 각각 left-heavy, right-heavy라는 것과 동일합니다.

그림으로 한번 살펴보겠습니다.

figure10. right-left 케이스

A, C 노드가 있었는데 B 노드가 새롭게 추가가 됩니다. C 노드의 왼쪽 서브트리로 추가가 되죠.

A노드의 오른쪽 서브트리(C가 루트노드인)의 왼쪽 서브트리에 새로운 노드가 추가가 됐습니다.

figure11. 오른쪽 회전

이런 경우 오른쪽으로 먼저 회전을 해줍니다.

figure12. 오른쪽 회전 결과

그러면?? 두 번째 케이스인 right-right 케이스가 됩니다. 그럼 왼쪽으로 회전시켜주면 되겠죠?

figure13. 왼쪽 회전
figure14. 균형 트리

자, 이렇게 마지막 네 번째 케이스도 균형 트리가 되었습니다.

 

AVL 트리의 4가지 회전 방법에 대해서 그림으로 설명을 해드렸는데요,

이해가 잘 되셨길 바랍니다.

 

이상으로 AVL 트리의 로테이션법에 대한 포스팅을 마치도록 하겠습니다.

 

오늘도 즐프하세요~

 

 

Note: left-left 케이스를 제외한 나머지 케이스들에 대한 이미지는 tutorialspoint.com 에서 가져왔습니다.

 

💻 Programming

[자료구조] AVL 트리 특징 및 로테이션 기준

AVL 트리 특징 및 로테이션 기준

AVL 트리는 이진탐색트리(Binary Search Tree, BST)의 한 종류입니다.

이진 탐색 트리(BST)는 이진트리(Binary Tree)의 한 종류이죠.

따라서 AVL 트리는 이진트리와 이진탐색트리의 특징들을 모두 갖고 있습니다.

그렇다면 그 특징들이 무엇일까요?

 

- 이진트리(Binary Tree)의 특징

a. 각 노드는 최대 2개의 자식 노드를 가질 수 있다.

b. 자식 노드는 보통 왼쪽과 오른쪽으로 구분한다 -> left child, right child

c. 각 노드는 데이터를 가지고 있다. (데이터를 저장하기 위한 자료구조 중 하나이니 당연한 말이다)

 

- 이진탐색트리(Binary Search Tree, BST)의 특징

a. 이진트리이므로 이진트리의 특성을 기본적으로 갖고 있다.

b. 각 노드의 값은 왼쪽 서브트리에 존재하는 모든 값들 보다 크고 오른쪽 서브트리에 존재하는 모든 값들 보다 작다.

c. 따라서 이진탐색트리는 정렬된 이진트리(sorted binary tree)라고도 한다. 

 

자, 그리고 여기에 추가로 아래와 같은 특징을 갖는 트리를 AVL 트리라고 부릅니다.

- AVL 트리의 특징

a. 어떤 노드라도 왼쪽 서브트리와 오른쪽 서브트리의 높이 차이가 1보다 크지 않다. (최대 높이 차이는 1 이다.)

=> Binary Tree이면서 최대 높이 차이가 1이라는 말은 height = O(log N) 이라는 것과 같다.

b. 만약 특정 노드를 추가하거나 삭제했을 때 왼쪽 서브트리와 오른쪽 서브트리의 높이 차이가 1보다 커지는 노드가 생긴다면, re-balancing을 통해 a 규칙에 어긋나지 않도록 한다.

=> 이 말은 AVL 트리는 자가 균형 이진 탐색 트리(self-balancing BST) 라는 말과 같다.

c. 균형 트리(balanced tree)이다. (좀더 정확히는 높이 균형 트리이다.)

=> 균형 트리는 아래 세 가지 조건을 모두 만족하는 트리이다.

   i. 왼쪽과 오른쪽 서브트리의 높이 차이가 최대 1이다.

  ii. 왼쪽 서브트리가 균형 트리이다.

 iii. 오른쪽 서브트리가 균형 트리이다.

d. 각 노드는 균형값(균형인수, balance factor)을 가지고 있으며 이 균형값은 오른쪽 서브트리의 높이에서 왼쪽 서브트리의 높이를 뺀 값이다. 이 값은 항상 {-1, 0, 1 } 셋 중 하나의 값이어야 한다.

e. 균형값(균형인수, balance factor)이 마이너스(-)이면 left-heavy, 플러스(+) 값을 가지면 right-heavy 라고 한다.

f. pivot 노드는 새로운 노드가 추가되었을 때 균형값에 변동이 발생하여 unbalanced 된 노드들 중 신규노드와 가장 가까운 노드를 말한다. 

 

자, 위와 같은 특징을 갖고 있는 AVL 트리에 노드를 추가 또는 삭제를 하게되면 스스로 균형을 맞추기 위해 트리의 회전(rotation)을 필요로 할 수도 있습니다. 노드가 추가 또는 삭제 되면서 일부 노드들의 균형값에 변동이 발생하기 때문입니다. 회전을 필요로 하는 경우는 크게 2가지(single rotation과 double rotation) 좀 더 세분화하면 아래와 같이 4가지로 나눌 수 있습니다.

  • 왼왼 (Left - Left) : 부모 노드와 pivot 노드의 균형값이 모두 left-heavy인 경우
    => Single Right rotation (오른쪽으로 1회전)
  • 오오 (Right - Right) : 부모 노드와 pivot 노드의 균형값이 모두 right-heavy인 경우
    => Single Left rotation (왼쪽으로 1회전)
  • 왼오 (Left - Right) : 부모 노드와 pivot 노드의 균형값이 각각 right-heavy, left-heavy, 인 경우
    => Double Left-Right rotation (왼쪽으로 한번, 오른쪽으로 한번 총 두 번의 회전)
  • 오왼(Right - Left) : 부모 노드와 pivot 노드의 균형값이 각각 left-heavy, right-heavy인 경우
    => Double Right-Left rotation (오른쪽으로 한번, 왼쪽으로 한번 총 두 번의 회전)

 

각각의 로테이션에 대한 그림은 wiki에 매우 간단하게 나와있어 gif 이미지를 첨부합니다.

AVL 트리의 4가지 로테이션 예제 (출처: https://en.wikipedia.org/wiki/AVL_tree)

로테이션 예제 그림이 너무 간단해서 위 4가지 케이스에 대해 설명이 잘 될지 모르겠습니다.

 

로테이션에 대한 추가 설명은 다음 포스팅을 기다려주세요~

 

추가로 AVL 트리에 새로운 노드를 추가하거나 삭제하는 동작은 최악의 상황에서도 O(log N) 의 시간복잡도를 보이며 검색 또한 O(log N) 으로 빠른 자료구조입니다.

 

 

 

 

AWS 로고

AWS 엘라스틱서치 키바나 에러 - Unable to find saved objects

최근 AWS의 엘라스틱서치 클러스터 이슈 때문에 시간을 많이 낭비했다.

AWS의 elastic search는 클러스터에 문제가 생기면 개발자가 어떻게 할 수 없기 때문에 매우 곤란할 수 있다.

클러스터도 키바나도 재시작을 할 수 없고 무조건 AWS internal team에서 확인해줘야한다.

support에 chat으로 문의해도 결국 internal team으로 넘어간다.

 

다행히 키바나가 접속이 된다면 클러스터 이슈는 아니다.

근데 키바나의 discover 메뉴에서 아무것도 조회가 안되고 하얀 화면이 뜨는 경우를 보게 되었다.

클러스터 상태와 키바나의 상태는 모두 GREEN 이었다.

특별히 설정을 변경하거나 하지 않았었는데도 갑자기 이런 현상이 생겼다.

ES 클러스터 인스턴스 타입을 변경하여 보았으나 현상은 동일했다.

그래서 키바나 인덱스를 삭제하여 설정을 초기화해보았다. 

인덱스 패턴을 새로 정의해주고 다시 discover로 들어갔는데....오잉?

다시 인덱스 패턴을 정의하라는 화면이 나온다.;;; 헐;;;

방금 정의해준 것에 대해 제대로 인식을 못한다;;;;

saved objects 를 들어가보니 에러가 발생한다.

 

Unable to find saved objects

 

이것저것 해보다가 AWS support에 live chat으로 문의했다.

GET _cat/aliases 를 실행해보라고 한다.

실행해보니 별다른 메시지는 없고 단순히 200 success 표시만 키바나 창 우상단에 표시되었다.

그 얘길 해주니 alias가 제대로 연결이 안된것 같다면서 아래 링크를 하나 던져주면서 모든 스텝을 그대로 따라서 해보라고 한다.

 

aws.amazon.com/ko/premiumsupport/knowledge-center/amazon-es-saved-objects-kibana/

 

Kibana에서 Amazon ES로부터 저장한 객체 찾기

Amazon ES 사용자들은 Amazon ES 버전 7.1로 업그레이드한 후에 가끔 400 Bad Request 오류를 경험할 수 있습니다. 업그레이드에 따라 사용자가 Kibana에서 저장된 객체를 찾을 수 없는 문제가 발생할 수 있

aws.amazon.com

스텝을 따라하면서 무슨 명령어인가 보았더니 새로운 인덱스를 만들고 alias를 연결해주고 기존 인덱스의 내용을 넣어주는 것이었다.

 

그리고 그렇게 따라하다가 제일 마지막 스텝인 백업 인덱스 삭제하기 전에 인덱스 패턴(saved object) 목록을 조회해보니 똑같은 인덱스 패턴이 여러개가 조회가 되었다. 그래서 동일한 패턴은 하나씩만 남겨놓고 모두 삭제하였다. 그리고 discover로 들어가니 정상적으로 조회가 되었다.

dev tools로 돌아와 GET _cat/aliases 를 실행해보니 200 success 표시와 함께 .kibana_1.kibana .kibana_1 - - - - 라고 메시지가 출력되었다.

 

오늘도 에러와 마주한 즐거운 하루였다.

reactjs 프론트와 java 백엔드 연동

이번에는 ReactJS로 구현한 프론트엔드와 Java + 스프링부트로 구현한 백엔드를 연동해보았습니다.

react를 쓰니 일반적인 자바스크립트 파일을 연결하는 것도 잘 모르겠고, ajax 호출을 jquery로만 했었는데 fetch 메서드를 쓰는 것이 공식문서의 예제로 나와있어서 그걸 이용했습니다. 

백엔드에 CORS 설정도 빼먹었었고요...

일단 화면에서 바뀐 점은 Blog > 다이어리 메뉴를 선택하면 검색을 위한 입력창과 버튼, 새글 등록을 위한 버튼을 넣었고(기능은 아직 구현못했네요), 리스트 목록 조회하는 부분만 간단하게 연결해놓았습니다. 소스코드는 많이 수정했는데 실제 화면에서 바뀐건 뭐 없네요 ㅡㅡㅋ

 

게시글 조회 기능 구현

 

앞으로 할 일들을 나열해봤습니다.

  • 등록기능 프론트 구현 및 백엔드와 연동
  • 검색기능 백엔드 구현 및 프론트와 연동
  • 목록의 헤더에 제목, 내용, 수정일 표시
  • 내용은 한 줄을 넘기지 않도록 일부만 출력되도록 수정
  • 등록일시 표시 -> 백엔드 수정
  • 내부적으로는 수정일시도 관리
  • 게시글 좌측에 게시글 번호 넣기 -> 몽고DB에 auto increment 기능이 없어 소스레벨에서 구현해야함

역시 프론트와 백엔드를 혼자 하려니 할 일이 너무 많네요 ㅜㅜ

그래도 재미있게 만들어 가고 있습니다.

요새 티스토리가 자체광고를 붙여서 광고가 자꾸 뜨는것 같네요...로그인할 때도 뜨고...

빨리 블로그 앱 만들어서 라이브해서 직접 사용해보고 싶네요

 

 

참고문서:

스프링부트 CORS 설정하기 >> spring.io/guides/gs/rest-service-cors/#global-cors-configuration

 

Enabling Cross Origin Requests for a RESTful Web Service

this guide is designed to get you productive as quickly as possible and using the latest Spring project releases and techniques as recommended by the Spring team

spring.io

리액트 ajax 호출하기 >> reactjs.org/docs/faq-ajax.html

 

AJAX and APIs – React

A JavaScript library for building user interfaces

reactjs.org

리액트 부모상태 업데이트하기 >> stackoverflow.com/questions/35537229/how-to-update-parents-state-in-react

 

How to update parent's state in React?

My structure looks as follows: Component 1 - |- Component 2 - - |- Component 4 - - - |- Component 5 Component 3 Component 3 should display some data depending on state of Component 5....

stackoverflow.com

 

💻 Programming

[5분코딩] 스프링 부트에 스웨거 v3.x 연동하기

자동생성된 스웨거 API 문서

스웨거는 백엔드에서 개발한 API를 문서화해주는 툴로 개발 진행중일 때 뿐만아니라 운영중에도 유용하게 사용되기도 하는데요

스웨거 버전이 3으로 올라가면서 설정 및 기본 url이 변경이 되었습니다.

스웨거 버전 업에 두어번 실패한 끝에 가장 간단히 해결할 수 있는 방법을 구해왔습니다.

 

스프링부트 스웨거 설정

버전은 스프링부트 2.4.0, 스웨거 3.0.0 입니다.

 

1. 스프링 부트와 스웨거 의존성 추가

메이븐프로젝트

<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-boot-starter</artifactId>
	<version>3.0.0</version>
</dependency>
<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger-ui</artifactId>
	<version>3.0.0</version>
</dependency>

 

그래들 프로젝트

repositories {
	jcenter()
}

dependencies {
	implementation "io.springfox:springfox-boot-starter:3.0.0"
	implementation "io.springfox:springfox-swagger-ui:3.0.0"
}

 

2. 설정하기

SwaggerConfig 클래스를 하나 만들어서 아래와 같이 @Configuration 어노테이션을 붙여주고 Docket 빈을 하나 생성하면 됩니다.

좀 더 상세한 설정을 하려면 공식사이트에서 확인가능합니다.

@Configuration
public class SwaggerConfig {

	@Bean
	public Docket api() {
	return new Docket(DocumentationType.SWAGGER_2)
		.select()
		.apis(RequestHandlerSelectors.any())
		.paths(PathSelectors.any())
		.build();
	}
}

 

3. 접속 URL

기존의 2.x 버전까지는 host<:port>/<context root>/swagger-ui.html 로 접속을 했었다면, v3부터는 host<:port>/<context root>/swagger-ui/ 또는 host<:port>/<context root>/swagger-ui/index.html 로 접속을 하면 됩니다.

 

 

 

참고문서: https://springfox.github.io/springfox/docs/current/

이번 프로젝트에서는 Java + SpringBoot + Mongo DB 로 구현할 예정입니다.

각각 사용할 버전은 다음과 같습니다.

 

- Java 8 (오라클jdk가 상용목적으로는 유료화되어 추후 코틀린으로 변경하는 프로젝트를 진행해봐야겠네요)

- Springboot 2.4.0 (20년 11월 현재 최신버전)

- Mongo DB 4.4.2 Community Server (20년 11월 현재 최신버전)

 

몽고디비 설치 관련해서는 몽고DB 최신버전 설치하기 포스팅을 참고하시면 됩니다.

설치 후 사용자 계정 생성 및 데이터 베이스 생성 관련해서는 [몽고DB] 기본명령어 포스팅을 참고해주세요.

 

이제 그래들 자바 프로젝트를 하나 생성하여 Blog > 다이어리 메뉴에서 사용할 CRUD를 순서대로 작성해보겠습니다.

가장 먼저 프로젝트 구성을 한번 살펴보겠습니다.

블로그 만들기 백엔드 프로젝트

 

우선 build.gralde파일을 아래와 같이 작성했습니다.

plugins {
    id "org.springframework.boot" version "2.4.0"
    id 'java'
}

group 'com.keichee'
version '1.0-SNAPSHOT'
sourceCompatibility = "1.8"

repositories {
    jcenter()
}

ext {
    springVersion = '2.4.0'
}

dependencies {
    testImplementation group: 'junit', name: 'junit', version: '4.12'

    implementation 'org.mongodb:mongodb-driver-sync:4.1.1'
    testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'

    implementation "org.springframework.boot:spring-boot-starter-web:$springVersion"
    testImplementation("org.springframework.boot:spring-boot-starter-test:$springVersion") {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }

    compileOnly 'org.projectlombok:lombok:1.18.16'
    annotationProcessor 'org.projectlombok:lombok:1.18.16'

    testCompileOnly 'org.projectlombok:lombok:1.18.16'
    testAnnotationProcessor 'org.projectlombok:lombok:1.18.16'

    implementation "io.springfox:springfox-boot-starter:3.0.0"
    implementation 'io.springfox:springfox-swagger-ui:3.0.0'

    implementation 'com.google.guava:guava:30.0-jre'
}

test {
    useJUnitPlatform()
}

 

mongodb-driversync 4.1.1 을 사용했습니다.

로깅을 위해서 logback-classic을 추가했고 

api 를 쉽게 만들 수 있도록 spring-boot-starter-web을 추가

getter, setter 등의 어노테이션 사용을 위해 lombok 라이브러리를 추가하였고

스웨거 문서를 사용하려고 springfox 라이브러리들도 추가했습니다.

 

이제 몽고DB와 연결을 위한 설정을 아래와 같이 해줍니다.

@Configuration
public class MongoConfig {

    private static final String host = "localhost";
    private static final int port = 27017;
    private static final String database = "blogapp";

    @Bean
    public MongoDatabase blogAppDatabase() {
        MongoClient client = MongoClients.create(
                MongoClientSettings.builder()
                        .applyToClusterSettings(builder ->
                                builder.hosts(Collections.singletonList(new ServerAddress(host, port))))
                        .build());
        return client.getDatabase(database);
    }
}

 

컨트롤러에는 딱 4개의 Restful API를 만들었습니다.

@Slf4j
@RestController
@RequestMapping("/diary/life")
@AllArgsConstructor
public class DiaryLifeController {

    private final DiaryLifeService diaryLifeService;

    @ApiOperation("전체 목록 조회")
    @GetMapping
    public Response<List<Post>> getPosts() {
        log.info("전체 목록 조회");

        return new Response<>(diaryLifeService.getPosts(null));
    }

    @ApiOperation("다이어리 포스팅 신규생성")
    @PostMapping
    public Response<Void> savePost(@RequestBody Post post) {
        validatePostInput(post);
        diaryLifeService.createPost(post);
        return new Response<>();
    }

    @ApiOperation("다이어리 포스팅 업데이트")
    @PutMapping
    public Response<Void> updatePost(@RequestBody Post post) {
        validatePostInput(post);
        diaryLifeService.updatePost(post);
        return new Response<>();
    }

    @ApiOperation("다이어리 포스팅 삭제")
    @DeleteMapping
    public Response<Void> deletePost(@RequestParam String _id) {
        diaryLifeService.deletePost(_id);
        return new Response<>();
    }

    private void validatePostInput(Post post) {
        if (ObjectUtils.isEmpty(post.getTitle())) {
            throw new BadRequestException("No TITLE exists.");
        }
        if (ObjectUtils.isEmpty(post.getContent())) {
            throw new BadRequestException("No CONTENT exists.");
        }
    }

    @ExceptionHandler
    public ResponseEntity<Response<String>> badRequest(BadRequestException e) {
        log.error("Bad request.., e-msg:{}", e.getMessage(), e);
        return ResponseEntity.badRequest().body(new Response<>(e.getMessage()));
    }
}

 

스웨거로 보면 다음처럼 나오게 됩니다.

다이어리 API 스웨거

 

스웨거를 이용해서 기능 테스트를 한번 해보겠습니다.

 

 

 

현재 구현된 기능은 모두 정상적으로 동작하는 것 까지 확인했습니다.

 

여기까지 작업하면서 쉽게 풀리지 않았던 부분은 몽고DB를 처음 사용하다보니 몽고DB 클라이언트를 이용한 CRUD만드는 부분이었습니다. 기존 RDS와는 다르게 ObjectID 로 핸들링을 해야하는 부분이 있었고, auto increment pk 설정을 따로 할 수 없었습니다. 만약 게시글의 번호가 필요하다면 어떻게 데이터를 저장해야할지 고민이 좀 되는 부분입니다. 각 게시글마다 시퀀스값을 넣어줘야하는데 구글링해서 나오는 것들은 synchronized 가 안될 것 처럼 보여서 테스트도 좀 해봐야 할 것 같습니다.

 

여기까지 백엔드의 기본적인 구현을 완료했습니다.

다음 포스팅에서는 프론트와 백엔드를 연결하는 부분에 대해서 올리겠습니다.

💻 Programming

[몽고DB] 기본 쉘 명령어

MongoDB 기본명령어

 

- 현재 사용중인 데이터베이스 확인

> db

- 데이터베이스 목록 조회

> show dbs

admin 0.000GB

config 0.000GB

local 0.000GB

 

- 데이터베이스 변경

(존재하지 않는 새 데이터베이스로도 변경가능, 실제 생성은 컬렉션이 추가될 때 되는 듯)

> use blogapp

switched to db blogapp

> show dbs

admin 0.000GB

config 0.000GB

local 0.000GB

 

- 현재 데이터베이스의 컬렉션 목록 조회

> show collections

diary_life

 

- 컬렉션 및 데이터 추가 (데이터는 json format으로 넣는다)

db.<collection>.insertOne(<데이터>);

> db.diary_life.insertOne({title: "테스트", content:"테스트 게시글입니다"});

{ "acknowledged" : true, "insertedId" : ObjectId("5fb9a86c88b63c276fc5d50b") }

 

- 컬렉션의 모든 데이터 조회 (find메서드에 empty doc을 파라미터로 전달)

> db.diary_life.find({});

{ "_id" : ObjectId("5fb9a86c88b63c276fc5d50b"), "title" : "테스트", "content" : "테스트 게시글입니다" }

 

- 특정 데이터 업데이트

> db.diary_life.update({"title":"테스트"}, {title: "테스트", content:"업데이트완료"});

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.diary_life.find({});

{ "_id" : ObjectId("5fb9a86c88b63c276fc5d50b"), "title" : "테스트", "content" : "업데이트완료" }

 

- 컬렉션의 doc 개수 조회

> db.diary_life.count();

1

 

- 특정 데이터 삭제

> db.diary_life.deleteOne({"title":"테스트"});

{ "acknowledged" : true, "deletedCount" : 1 }

> db.diary_life.count(); 0

> db.diary_life.find({});

>

 

 

(참고: https://docs.mongodb.com/manual/reference/method/ )

아래 링크로 들어가서 오른쪽 하단의 Download 버튼을 클릭하여 tgz 파일을 다운로드 받아 압축을 풀면 설치가 완료된다.

 

몽고DB 커뮤니티 서버 최신버전 다운로드

 

 

몽고디비 최신버전은 자바버전 8 이상이면 사용가능하다.

추가적인 호환성 여부는 몽고DB 공식사이트에서 확인가능하다.

현재 사용하고 있는 자바 버전이 8이 아니라면 업그레이드가 필요하다.

몽고DB 드라이버 호환성 여부
몽고DB 자바버전 호환성 여부


몽고DB서버 실행하기 공식문서를 보고 따라하면되는데 그대로 따라하면 중간에 오류가 발생한다.

아래 명령어를 보고 실행하되 경로는 본인의 입맛에 맞게 변경해주면 된다.

keichee:BlogApp KH$ sudo mkdir -p /usr/local/mongodb/data-v4.4
Password:
keichee:BlogApp KH$ sudo mkdir -p /usr/local/mongodb/log
keichee:BlogApp KH$ sudo chown KH /usr/local/mongodb/*
keichee:BlogApp KH$ touch /usr/local/mongodb/log/mongo.log

-- 몽고db bin 디렉토리로 이동 --
keichee:bin KH$ ./mongod --dbpath /usr/local/mongodb/data-v4.4 --logpath /usr/local/mongodb/log/mongo.log --fork
about to fork child process, waiting until server is ready for connections.
forked process: 38133
child process started successfully, parent exiting
keichee:bin KH$ 

 

이제 서버는 정상적으로 기동이 되었고, 실제로 접속해서 명령어를 입력하려면 mongo 명령어를 실행하면 됩니다.

keichee:bin KH$ ./mongo
MongoDB shell version v4.4.2
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("b8192555-a975-457e-ab16-2b2c7675c72a") }
MongoDB server version: 4.4.2
---
The server generated these startup warnings when booting: 
        2020-11-22T08:12:57.220+09:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
        2020-11-22T08:12:57.221+09:00: This server is bound to localhost. Remote systems will be unable to connect to this server. Start the server with --bind_ip <address> to specify which IP addresses it should serve responses from, or with --bind_ip_all to bind to all interfaces. If this behavior is desired, start the server with --bind_ip 127.0.0.1 to disable this warning
        2020-11-22T08:12:57.221+09:00: Soft rlimits too low
        2020-11-22T08:12:57.221+09:00:         currentValue: 10240
        2020-11-22T08:12:57.221+09:00:         recommendedMinimum: 64000
---
---
        Enable MongoDB's free cloud-based monitoring service, which will then receive and display
        metrics about your deployment (disk utilization, CPU, operation statistics, etc).

        The monitoring data will be available on a MongoDB website with a unique URL accessible to you
        and anyone you share the URL with. MongoDB may use this information to make product
        improvements and to suggest MongoDB products and deployment options to you.

        To enable free monitoring, run the following command: db.enableFreeMonitoring()
        To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---
>