2017년 11월 29일 수요일

토비의 스프링 공부/정리 [3]

회사에서 토비의 스프링 스터디를 시작하였다.
새로운 기분으로 정독하고 있으며, 정리한 내용을 학습목적으로 git에 올리고 있다.
이해가 가지 않는 내용도 있지만, 일단 1회독을 마치는 것을 목표로 진행하자!









//////////////////////////////////////////////////////////////////////////////////////////////


3. 템플릿


템플릿이란 바뀌는 성질이 다른 코드 중에서 변경이 거의 일어나지 않으며 일정한 패턴으로 유지되는 특성을 가진 부분을 자유롭게 변경되는 성질을 가진 부분으로 독립시켜서 효과적으로 활용할 수 있도록하는 방법. (??)
3장에선 스프링에 적용된 템플릿 기법을 살펴보고, 이를 적용해 완성된 DAO코드를 만드는 방법을 소개한다.


3.1 다시 보는 초난감 DAO

책에서 소개한 UserDao 코드에는 예외상황에 대한 처리 부분에 문제점이 있다고 한다.
preparedstatement를 처리하는 중 예외가 발생하면 close를 실행못하고 마치게된다. 이것이 계속쌓이면 리소스가 부족하다는 오류가 발생할 수 있다. 그래서 try/catch 구문으로 예외처리를 하도록 권장한다.
글 조회부분 또한 ResultSet도 반환해야하는 리소스이기 때문에 예외상황에서도 ResultSet의 close()메소드가 반드시 호출되도록 만들어줘야 한다.

리소스 반환과 close()

Connection이나 PreStatement에는 close()메소드가 있다. 종료라고 볼 수도 있지만 보통 리소스를 반환한다는 의미로 이해하는 것이 좋다. Connection과 PreparedStatement는 보통 풀(pool) 방식으로 운영된다. 미리 정해진 풀 안에 제한된 수의 리소스(Connection, Statement)를 만들어 두고 필요할 때 이를 할당하고, 반환하면 다시 풀에 넣는 방식으로 운영된다. 요청이 매우 많은 서버 환경에서는 매번 새로운 리소스를 생성하는 대신 풀에 미리 만들어둔 리소스를 돌려가며 사용하는 편이 훨씬 유리하다. 대신. 사용한 리소스는 빠르게반환해야 한다. 그렇지 안흥면 풀에 있는 리소스가 고갈되고 결국 문제가 발생한다. close()메소드는 사용한 리소스를 풀로 다시 돌려주는 역할을 한다.

3.2 변하는 것과 변하지 않는 것

finally 블록의 c.close() 라인 하나 빼먹은 것과 같은 실수를 했어도 테스트를 돌려보면 별 문제가 없어 보인다. 하지만 해당 메소드가 호출되고 나면 커넥션이 하나씩 반환되지 않고 쌓여가게 된다. 이를 그대로 사용하면 최대 DB커넥션 개수를 넘어설 것이고, 서버에서 리소스가 꽉 찼다는 에러가 나면서 서비스가 중단되는 상황이 발생한다.
>>이런 예외상황을 처리하는 코드는 테스트하기 매우 어렵고 번거롭다.
>> 효과적으로 다루기위해선..??

분리와 재사용을 위한 디자인 패턴 적용

책에서는 UserDao의 메소드를 개선하고 있다.
*****어려움 ㅠㅠ

3.3 JDBC 전략 패턴의 최적화


3.4 컨텍스트와 DI


3.5 템플릿과 콜백


3.6 스프링의 JdbcTemplate

2017년 11월 6일 월요일

토비의 스프링 공부/정리 2장

회사에서 토비의 스프링 스터디를 시작하였다.
새로운 기분으로 정독하고 있으며, 정리한 내용을 학습목적으로 git에 올리고 있다.
이해가 가지 않는 내용도 있지만, 일단 1회독을 마치는 것을 목표로 진행하자!



2. 테스트

저자는 스프링이 개발자에게 제공하는 것들 중 객체지향과 테스트가 가장 중요한 가치라고 하며 2장을 시작한다.
스프링으로 개발을 하면서 테스트를 만들지 않는다면 이는 스프링이 지닌 가치의 절반을 포기하는 셈이다.

테스트란? 
- 코드의 결함 제거(디버깅) --> 의도한대로 나올 수 있도록 수정해 나가는 작업 
- 내가 예상하고 의도했던 대로 코드가 정확히 동작하는지를 확인해서 만든 코드를 확신할 수 있게 해주는 작업 

책에서는 웹을 통한 DAO테스트 방법의 2가지 문제점에 대해 말하고 있다. 
1. 수동 확인 작업의 번거로움: 테스트 결과를 확인하는 작업을 사람의 눈으로 해야함. 
2. 실행 작업의 번거로움: 전체 기능을 테스트해보기 위해 main 메소드를 수백 번 실행하는 수고가 필요함.
  • ++DAO뿐만 아니라 서비스 클래스, 컨트롤러, 뷰 등 모든 레이어의 기능을 다 만들고 나서야 테스트가 가능 (이 경우 오류가 발생했을 때 어느부분에서 오류가 발생하였는지 파악이 어렵고, 빠르고 정확히 대응하기 어려움)
단위테스트란?(Unit Test)
- 작은 단위의 코드에 대해 테스트를 수행한 것
- 테스트의 관심이 다르다면 테스트할 대상을 분리하고 집중해서 접근해야한다.
- 단위 테스트를 하는 이유는 개발자가 설계하고 만든 코드가 원래 의도한 대로 동작하는지를 
개발자 스스로 빨리 확인 받기 위해서이다.
이 후 이 2가지 문제점에 대해 개선하는 작업을 보여주고 있다.
  1. 테스트 검증의 자동화
    • 테스트의 결과 값들을 사람의 눈으로 직접 확인하는 것이 아닌 테스트 수행과 기대하는 결과에 대한 확인까지 해주는 코드로 된 자동화된 테스트를 해야한다고 하고있다.
  2. 테스트의 효율적인 수행과 결과 관리
  • main() 메소드를 이용한 테스트 작성 방법만으로는 애플리케이션 규모가 커지고 테스트 개수가 많이지면 테스트를 수행하는 일이 점점 부담이 된다며, JUnit 을 소개하고있다.
JUnit 프레임워크
자바 테스팅 프레임워크로 JUnit을 통해 단위테스트가 가능하다.
책에서는 스프링을 학습하고 제대로 활용하려면 최소한의 JUnit 테스트 작성 방법과 실행방법은 알고있어야한다고 하고있다.
스프링의 핵심 기능 중 하나인 스프링 테스트 모듈도 JUnit을 이용하고 있다고 한다.
JUnit의 간단한 사용법과 테스트 방법을 설명한 후 테스트 결과의 일관성을 설명하고 있다. 
(책에서는 사용자를 추가하는 테스트를 수행하고있는데, 한번 수행 후 또 다시 테스트를 실행시키면 사용자가 중복되기 때문에 문제가 발생하기 때문에 테스트 전에 등록된 사용자 정보를 초기화 시켜주는 작업을 하여 동일한 테스트 결과를 나타날 수 있게 하는 과정을 설명하고있다.)
  - 반복적으로 테스트를 수행하였을 때, 항상 동일한 결과가 나와야한다.
  - 단위테스트는 코드가 바뀌지 않는다면 매번 실행할 때마다 동일한 테스트 결과를 얻을 수 있어야 한다.
"항상 네거티브 테스트를 먼저 만들라" 
부정적인 케이스를 먼저 만드는 습관을 들이는게 좋다.
테스트 주도 개발(Test Driven Development-TDD)이란?
만들고자 하는 기능의 내용을 담고 있으면서 만들어진 코드를 검증도 해줄 수 있도록
테스트 코드를 먼저 만들고, 테스트를 성공하게 해주는 코드를 작성하는 방식의 개발 방법
("실패한 테스트를 성공시키기 위한 목적이 아닌 코드는 만들지 않는다")
TDD의 장점
 코드를 만들어 테스트를 실행하는 그 사이의 간격이 매우 짧다.
(오류를 빨리 발견할 수 있다.)

스프링은 JUnit을 이용하는 테스트 컨텍스트 프레임워크를 제공한다. 라고하며, 의존성 주입을 이용한 테스트를 소개하고 있다. (이 부분은 직접 실습을 해보면서 하는 게 좋을 듯 하다.)
@Autowired가 잠깐 나오는데, 2권에서 자세하게 설명한다고 되어 있다.
- Autowired 가 붙은 인스턴스 변수가 있으면, 테스트 컨텍스트 프레임워크는 변수 타입과 일치하는 컨텍스트 내의 빈을 찾는다. 
타입이 일치하는 빈이 있으면 인스턴스 변수에 주입해준다.

학습테스트(learning test)란?
자신이 만들지 않은 프레임워크나 다른 개발팀에서 만들어서 제공한 라이브러리 등에 대해서는 테스트를 작성해야한다.

-목적: 자신이 사용할 API나 프레임워크의 기능을 테스트로 보면서 사용 방법을 익히려는 것
-장점
1. 다양한 조건에 따른 기능을 손십게 확인 가능
2. 학습 테스트 코드를 개발 중에 참고할 수 있음.
3. 프레임워크나 제품을 업그레이드할 때 호환성 검증을 도와줌.
4. 테스트 작성에 대한 좋은 훈련이 됨.
5. 새로운 기술을 공부하는 과정이 즐거워짐.

버그테스트(bug test)란?
코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트
(일단 실패하도록 만들어야함. 그 후 성공할 수 있도록 애플리케이션 코드를 수정함.)
필요성과 장점
1. 테스트의 완성도를 높여줌
2. 버그의 내용을 명확하게 분석하게 해줌
3. 기술적인 문제를 해결하는 데 도움이 됨


동등분할: 같은 결과를 내는 값의 범위를 구분해서 각 대표값으로 테스트 하는 방법

경계값 분석: 에러는 동등분할 범위의 경계에서 주로 많이 발생한다는 특징을 이용해서 경계의 근처에 있는 값을 이용해 테스트하는 방법.







//////////////////////////////////////////////////////////////////////////////////////////////


2. 테스트

스프링이 개발자에게 제공하는 가장 중요한 가치는? >> 객체지향과 테스트
스프링은 IoC/Di를 이용해 객체지향 프로그래밍 언어의 근본과 가치를 개발자가 손쉽게 적용하고 사용할 수 있게 도와주는 기술, 동시에 복잡한 엔터프라이즈 애플리케이션을 효과적으로 개발하기 위한 기술. 
테스트는 스프링이 강조하고 가치를 두고 있다. 테스트를 통해 만들어진 코드를 확신할 수 있게 해주고, 변화에 유연하게 대처할 수 있는 자신감을 준다. 
2장에서는 테스트란 무엇이며, 그 가치와 장점, 활용 전략, 스프링과의 관계를 살펴본다.

2.1 UserDaoTest 다시보기

테스트란 내가 예상하고 의도했던 대로 코드가 정확히 동작하는지를 확인해서, 만든 코드를 확신할 수 있게 해주는 작업. 테스트의 결과가 원하는대로 나오지 않을 경우 코드나 설계에 결함이 있음을 알 수 있음. 이를 통해 코드의 결함을 제거해가는 작업, 일명 디버깅을 거치게 되고, 결국 최종적으로 테스트가 성공하면 모든 결함이 제거됐다는 확신을 얻을 수 있음.

웹을 통한 DAO테스트 방법의 문제점

DAO뿐만 아니라 서비스 클래스, 컨트롤러, JSP 뷰 등 모든 레이어의 기능을 다 만들고나서 테스트가 가능하다. 이 때문에 테스트도중 에러가 발생하면 에러를 간단히 찾을 수 없게 된다. >> 다른 계층의 코드와 컴포넌트, 심지어 서버의 설정 상태까지 모두 테스트에 영향을 줄 수 있기 때문에 이런 방식으로 테스트하는 것은 번거롭고, 오류가 있을 때 빠르게 대응이 어렵다.

작은 단위의 테스트

테스트는 가능하면 작은 단위로 쪼개어 집중해서 할 수 있어야 한다. 관심사의 분리라는 원리는 여기에도 적용됨. 
작은 단위의 코드에 대해 테스트를 수행한 것을 단위 테스트라 한다. 
>> 단위 테스트 없이 긴 테스트를 수행하는 경우, 수많은 에러가 발생하거나 에러가 안나지만 기능이 제대로 동작하지 않을 수 있음. 이때 문제의 원인을 찾기 힘들다.  하지만 단위 별로 테스트를 진행한 후 긴 테스트를 한다면, 에러나 실패가 발생할 수 도 있겠지만, 이미 각 단위별로 검증을 마친 상태이므로 훨씬 더 나을 것임이다.

자동수행 테스트 코드

테스트는 자동으로 수행되도록 코드로 만들어지는 것이 중요함.
번거로운 작업 없이 테스트를 빠르게 실행 할 수 있기 때문에 언제든 코드를 수정하고나서 테스트를 해볼 수 있음. 

지속적인 개선과 점직적인 개발을 위한 테스트

테스트를 통해 기능을 추가해가면서 점직적인 개발이 가능하고 기존에 만들었던 기능에 영향을 주지 않고 확인할 수 있음.


책에서 소개한 UserDaoTest의 문제점을 서술하고 있다.

수동확인 작업의 번거로움: 테스트의 수행은 자동으로 진행되지만 결과확인은 사람의 책임이므로 완전히 자동으로 테스트되는 방법이라고 할 수 없음.

실행 작업의 번거로움: 매번 main() 메소드를 실행하는 것은 분거로움으로 좀 더 편리하고 체계적으로 테스트를 실행하고 그 결과를 확인하는 방법이 필요.

2.2 UserDaoTest 개선

테스트 검증의 자동화

책에선 가져오는 오브젝트의 값을 비교해서 확인하는 것을 추가하여 첫번째 문제점인 결과확인 작업까지 테스트 코드에 추가하였다. 

테스트의 효율적인 수행과 결과 관리

좀 더 편리하게 테스트를 수행하고 편리하게 결과를 확인하려면 단순한 main 메소드로는 한계가 있음. 일정한 패턴을 가진 테스트를 만들 수 있고, 많은 테스트를 간단히 실행시킬 수 있으며, 테스트 결과를 종합해서 볼 수 있고, 테스트가 실패한 곳에서 빠르게 찾을 수 있는 기능을 갖춘 테스트 지원 도구와 그에 맞는 테스트 작성 방법이 필요.
자바에서는 실용적인 테스트를 위한 도구가 여러가지 존재함. >> JUnit를 통해 자바로 단위 테스트를 만들 때 유용하게 쓸 수 있다.

JUnit 테스트로 전환

JUnit은 프레임워크이다. 1챕터에서 프레임워크의 기본 동작원리가 바로 제어의 역전(IoC)라고 했다. 프레임워크는 개발자가 만든 클래스에 대한 제어 권환을 넘겨받아서 주도적으로 애플리케이션의 흐름을 제어한다. 개발자가 만든 클래스의 오브젝트를 생성하고 실해아는 일은 프레임워크에 의해 진행된다. 따라서 프레임워크에서 동작하는 코드는 main()메소드가 필요 없고 오브젝트를 만들어서 실행시키는 코드를 만들 필요도 없다.


테스트 메소드 전환

기존의 책에서 소개한 main()메소드 테스트는 이런 면에서 프레임워크에 적용하기에 적합하지 않다. >> 테스트가 main()메소드로 만들어졌다는 것은 제어권을 직접 갖는다는 의미이기 때문
책에서 새로 만들 테스트 메소드는 JUnit 프레임워크가 요구하는 조건 두 가지를 따라야한다고 한다. 1. 메소드가 public으로 선언 2. 메소드에 @Test라는 어노테이션 붙이기


검증 코드 전환

책에서 앞서 소개한 결과를 확인할 때 사용한 if/else문을 asserThat이라는 스태틱 메소드를 이용해 변경하고 있다. 

2.3 개발자를 위한 테스팅 프레임워크 JUnit

스프링을 학습하고 제대로 활용하려면 최소한의 JUnit 테스트 작성 방법과 실행 방법은 알고 있어야 한다.
JUnitCore(JUnit API 인듯?)를 이용해 테스트를 실행하고 콘솔에 출력된 메시지를 보고 결과를 확인하는 ㅂ아법은 가장 간단하긴 하짐나 테스트의 수가 많아지면 관리하기 힘들어지므로 자바 IDE에 내장된 JUnit 테스트 지원도구를 사용하는 것이 좋다.

IDE
이클립스에서 @Test가 들어있는 테스트클래스를 선택한 뒤 run as 항목 중 junit test를 선택하면 테스트를 실행시킬 수 있다. >> 테스트의 총 수행시간, 실행한 테스트의 수, 테스트 에러의 수, 테스트 실패의 수를 확인할 수 있음.
빌드 툴
프로젝트의 빌드를 위해 ANT나 메이븐 같은 빌드 툴과 스크립트를 사용하고 있다면, 빌드 툴에서 제공하는 JUnit 플러그인이나 태스크를 이용해 JUnit테스트를 실행할 수 있다.
>>여러 개발자가 만든 코드를 통합해서 테스트를 수행해야할 때는 서버에서 모든 코드를 가져와 통합하고 빌드한 뒤에 테스트를 수행하는 것이 좋다고 함...


테스트 결과의 일관성

데이터베이스를 사용하는 테스트 후 데이터가 남아있다면 다음 테스트 때 오류가 발생할 수 도 있기때문에 테스트 후 이전상태의 데이터로 만들어줘야 한다. 
책에선 deleteAll()라는 메소드를 추가하여 데이터를 지우고 테스트를 시작한다.


>>개발자는 빨리 테스트를 만들어 성공하는 것을 보고 다음 기능으로 나아가고 싶어하기 때문에, 긍정적인 경우를 골라서 성공할만한 테스트를 먼저 작성하게 되기 쉽다. 그래서 테스트를 작성할 때 부정적인 케이스를 먼저 만드는 습관을 들이는 것이 좋다고 함. (ex id를 정확하게 가져오는 것도 중요하지만, 존재하지 않는 id가 주어졌을때 어떻게 반응할지를 먼저 결정하고 이를 확인할 수 있는 테스트를 만들려고 한다면 예외적인 상황을 빠뜨리지 않는 꼼꼼한 개발이 가능하다고 한다.


테스트 주도 개발(Test Driven Development, TDD)

만들고자 하는 긴으의 내용을 담고 있으면서 만ㄷ느렁진 코드를 검증도 해줄 수 있도록 테스트 코드를 먼저 만들고, 테스트를 성공하게 해주는 코드를 작성하는 방식의 개발 방법.
>>아예 테스트를 먼저 만들고 그 테스트가 성공하도록 하는 코드만 만드는 식으로 진행

JUnit이 하나의 테스트 클래스를 가져와 테스트를 수행하는 방식

1. 테스트 클래스에서 @Test가 붙은 public이고 void형이며 파라미터가 없는 테스트 메소드를 모두 찾는다.
2. 테스트 클래스의 오브젝트를 하나 만든다.
3. @before가 붙은 메소드가 있으면 실행한다.
4. @Test가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둔다.
5. @After가 붙은 메소드가 있으면 실행한다.
6. 나머지 테스트 메소드에 대해 2~5번을 반복
7. 모든 테스트의 결과를 종합해서 돌려준다. 

픽스처

테스트를 수행하는데 필요한 정보나 오브젝트
>>책에선 테스트를 하기위한 유저정보(픽스처인듯?)를 @Before 를 통해 선언

2.4 스프링 테스트 적용

테스트를 위한 애플리케이샨 컨텍스트 관리

책에서 DI를 통해 오브젝트를 주입받아 테스트를 하는 방법을 소개하고 있다.
(생략 or 더 추가??)

@Autowired

스프링의 DI에 사용되는 특별한 어노테이션, @Autowired가 붙은 인스턴스 변수가 있으면, 테스트 컨텍스트 프레임워크는 변수 타입과 일치하는 컨텍스트 내의 빈을 찾는다. 타입이 일치하는 빈이 있으면 인스턴스 변수에 주입해준다.  


침투적 기술과 비침투적 기술

침투적(invasive) 기술은 기술을 적용했을 때 애플리케이션 코드에 기술 관련 API가 등장하거나, 특정 인터페이스나 크랠스를 사용하도록 강제하는 기술, 침투적 기술을 사용하면 애플리케이션 코드가 해당 기술에 종속되는 결과를 가져옴
비침투적(noninvasive) 기술은 애플레키에션 로직을 담은 코드에 아무런 영향을 주지 않고 적용가능. 따라서 기술에 종속적이지 않은 순수한 코드를 유지 
스프링은 비침투적 기술의 대표적 예
>>책에서 @autowird를 이용하지 않고 직접 오브젝트를 생성하여 테스트하는 방법을 소개하고 있다. 

(책에선 DI를 테스트에 이용하는 세가지 방법을 소개한 후 장점들을 소개하였음.)


2.5 학습 테스트로 배우는 스프링

학습테스트: 자신이 만들지 않은 프레임워크나 다른 개발팀에서 만들어서 제공한 라이브러리 등에 대해서도 테스트를 작성하는 것
>> 이를 통해 자신이 사용할 API나 프레임워크의 기능을 테스트로 보면서 사용방법을 익히려는 것


학습테스트의 장점

1. 다양한 조건에 따른 기능을 손쉽게 확인해 볼 수 있음.
2. 학습 테스트 코드를 개발 중에 참고할 수 있음.
3. 프레임워크나 제품을 업그레이드할 때 호환성 검증을 도와줌.
4. 테스트 작성에 대한 좋은 훈련이 됨.
5. 새로운 기술을 공부하는 과정이 즐거워짐. 


버그테스트

코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트.
버그테스트는 버그가 원인 되서 테스트가 실패하는 코드를 만들도록 해야함. 이 후 버그 테스트가 성공할 수 있도록 애플리케이션의 코드를 수정한다. 

버그테스트의 장점
1. 테스트의 완성도를 높여줌.
2. 버그의 내용을 명확하게 분석하게 해줌.(버그로 인해 발생할 수 있는 다른 오류들을 발견할 수 있고 이를 테스트의 중요한 기법 중의 하나인 동등분할이나 경계값 분석을 적용해 볼 수 있음)
3. 기술적인 문제를 해결하는데 도움.

동등분할(equivalence partitioning)
같은 결과를 내는 값의 범위를 구분해서 각 대표 값으로 테스트하는 방법. 어떤 작업의 결과의 종류가 true, false 또는 예외발생 세가지라면 각 결과를 내는 입력 값이나 상황의 조합을 만들어 모든 경우에 대한 테스트를 해보는 것이 좋다.
경계값 분석(boundary value analysis) 
에러는 동등 분할 범위의 경게에서 주로 많이 발생한다는 특징을 이용해서 경계의 근처에 있는 값을 이용해 테스트하는 방법. 보통 숫자의 입력 값인 경우 0 이나 그 주변 값 또는 정수의 최대값, 최소값 등으로 테스트해보면 도움이 될 때가 많음. 

2017년 11월 5일 일요일

Node.js 공부[6]

6챕터-데이터베이스 사용하기

웹 서버가 사용자 요청을 받으면 데이터베이스에 있는 데이터를 조회하여 응답하거나 또는 사용자가 보낸 데이터를 데이터베이스에 저장한다. 데이터베이스 유형은 데이터를 메모리에 저장하는 형태부터 오라클이나 MySQL 같은 관계형 데이터베이스, 몽고디비와 같은 NoSQL에 이르기까지 다양하다. 특히 몽고디비는 자비스크립트 객체를 그대로 저장할 수 있어서 자바스크립트 언어를 사용하는 노드에서 데이터를 저장하기 좋은 데이터베이스이다. 책에선 몽고디비로 데이터를 저장하고 조회하는 방법을 소개하고 있다.

몽고디비 시작하기

실무에서는 오라클이나 MySQL과 같은 관계형 데이터베이스를 많이 사용한다. 그러나 최근 비관계형 데이터베이스를 적용하는 곳이 늘고 이쓰며, 이런 시스템을 NoSQL 또는 Not Only SQL 이라고 한다. 

관계형 데이터베이스는 시스템의 신뢰도를 높이는데 필요한 장치를 많이 가지고 있다. 또 SQL문을 읽어 들이고 실행하는 데 많은 리소스를 사용하며 이 떄문에 성능이 떨어지는 경우가 많다. 이에 반해 NoSQL 데이터베이스는 성능을 최우선으로 생각하기 때문에 실시간으로 처리해야 하는 경우나 대용량 트래픽을 감당할 수 있는 메시징 시스템 등에 활용된다. 특히 클라우드 서비스로 서버를 구성하는 경우가 많아지면서 많은 사용자를 수용하거나 시스템 자원을 적게 소모하는  NoSQL 데이터베이스에 점점 더 관심을 갖게 되었다고 한다.

몽고디비는 NoSQL이기 때문에 관계형데이터베이스의 테이블의 개념이 없다. 그 대신 몽고디비는 여러 데이터가 모인 하나의 단위를 컬렉션(Collection)이라고 부른다. 테이블과 달리 데이터를 정해 놓은 칼럼의 형태대로 컬렉션에 넣어야 한다는 제약이 없다. 

설치 후 시스템 환경 변수 추가를 한다. 

익스프레스에서 몽고디비 사용하기

앞서 사용한 코드들을 이용하여 Databaseexample 폴더에 app.js 파일을 생성한다.
폴더에 npm init 을 통해 package.json 파일을 생성한 후 필요한 모듈들을 설치한다. 
(npm install express --save 등등)

package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
  "name": "package",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "dependencies": {
    "body-parser": "^1.18.2",
    "cookie-parser": "^1.4.3",
    "errorhandler": "^1.5.0",
    "express": "^4.16.2",
    "express-error-handler": "^1.1.0",
    "express-session": "^1.15.6",
    "http": "0.0.0",
    "mongodb": "^2.2.33",
    "path": "^0.12.7",
    "serve-static": "^1.13.1"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
cs
이 파일의 dependencies 정보는 새로운 프로젝트를 생성할때 그대로 사용 가능하다. 새 프로젝트를 만들었을때 이 파일과함께 npm install 명령을 해주면 이 정보대로 설치가 된다.


<mongodb를 이용한 간단한 아이디 체크,추가>
login.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>로그인 테스트</title>
    </head>
    
<body>
    <h1>로그인</h1>
    <br>
    <form method="POST" action="/process/login">
        <table>
            <tr>
                <td><label>아이디</label></td>
                <td><input type="text" name="id"></td>
            </tr>
            
            <tr>
                <td><label>비밀번호</label></td>
                <td><input type="password" name="password"></td>
            </tr>
        </table>
        <input type="submit" value="전송" />
    </form>
</body>
 
 
</html>

cs
adduser.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>사용자추가 테스트</title>
    </head>
<body>
    <h1>사용자추가</h1>
    <br>
    <form method="post" action="/process/adduser">
        <table>
            <tr>
                <td><label>아이디</label></td>
                <td><input type="text" name="id" /></td>
            </tr>
            <tr>
                <td><label>비밀번호</label></td>
                <td><input type="password" name="password" /></td>
            </tr>
            <tr>
                <td><label>사용자명</label></td>
                <td><input type="text" name="name" /></td>
            </tr>
        </table>
        <input type="submit" value="전송" name="" />
    </form>
</body>
</html>
cs

app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
/**
 * 데이터베이스 사용하기
 * 
 * 몽고디비에 사용자 추가하기
 
 * 웹브라우저에서 아래 주소의 페이지를 열고 웹페이지에서 요청
 *    http://localhost:3000/public/adduser.html
 *
 * @date 2016-11-10
 * @author Mike
 */
 
 
// Express 기본 모듈 불러오기
var express = require('express')
  , http = require('http')
  , path = require('path');
 
// Express의 미들웨어 불러오기
var bodyParser = require('body-parser')
  , cookieParser = require('cookie-parser')
  , static = require('serve-static')
  , errorHandler = require('errorhandler');
 
// 에러 핸들러 모듈 사용
var expressErrorHandler = require('express-error-handler');
 
// Session 미들웨어 불러오기
var expressSession = require('express-session');
 
// 몽고디비 모듈 사용
var MongoClient = require('mongodb').MongoClient;
 
 
// 익스프레스 객체 생성
var app = express();
 
 
// 기본 속성 설정
app.set('port', process.env.PORT || 3000);
 
// body-parser를 이용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({ extended: false }))
 
// body-parser를 이용해 application/json 파싱
app.use(bodyParser.json())
 
// public 폴더를 static으로 오픈
app.use('/public', static(path.join(__dirname, 'public')));
 
// cookie-parser 설정
app.use(cookieParser());
 
// 세션 설정
app.use(expressSession({
    secret:'my key',
    resave:true,
    saveUninitialized:true
}));
 
 
 
//===== 데이터베이스 연결 =====//
 
// 데이터베이스 객체를 위한 변수 선언
var database;
 
//데이터베이스에 연결
function connectDB() {
    // 데이터베이스 연결 정보
    var databaseUrl = 'mongodb://localhost:27017/local';
    
    // 데이터베이스 연결
    MongoClient.connect(databaseUrl, function(err, db) {
        if (err) throw err;
        
        console.log('데이터베이스에 연결되었습니다. : ' + databaseUrl);
        
        // database 변수에 할당
        database = db;
    });
}
 
 
 
//===== 라우팅 함수 등록 =====//
 
// 라우터 객체 참조
var router = express.Router();
 
// 로그인 라우팅 함수 - 데이터베이스의 정보와 비교
router.route('/process/login').post(function(req, res) {
    console.log('/process/login 호출됨.');
 
    // 요청 파라미터 확인
    var paramId = req.body.id || req.query.id;
    var paramPassword = req.body.password || req.query.password;
    
    console.log('요청 파라미터 : ' + paramId + ', ' + paramPassword);
    
    // 데이터베이스 객체가 초기화된 경우, authUser 함수 호출하여 사용자 인증
    if (database) {
        authUser(database, paramId, paramPassword, function(err, docs) {
            if (err) {throw err;}
            
            // 조회된 레코드가 있으면 성공 응답 전송
            if (docs) {
                console.dir(docs);
 
                // 조회 결과에서 사용자 이름 확인
                var username = docs[0].name;
                
                res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
                res.write('<h1>로그인 성공</h1>');
                res.write('<div><p>사용자 아이디 : ' + paramId + '</p></div>');
                res.write('<div><p>사용자 이름 : ' + username + '</p></div>');
                res.write("<br><br><a href='/public/login.html'>다시 로그인하기</a>");
                res.end();
            
            } else {  // 조회된 레코드가 없는 경우 실패 응답 전송
                res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
                res.write('<h1>로그인  실패</h1>');
                res.write('<div><p>아이디와 패스워드를 다시 확인하십시오.</p></div>');
                res.write("<br><br><a href='/public/login.html'>다시 로그인하기</a>");
                res.end();
            }
        });
    } else {  // 데이터베이스 객체가 초기화되지 않은 경우 실패 응답 전송
        res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
        res.write('<h2>데이터베이스 연결 실패</h2>');
        res.write('<div><p>데이터베이스에 연결하지 못했습니다.</p></div>');
        res.end();
    }
    
});
 
 
 
// 사용자 추가 라우팅 함수 - 클라이언트에서 보내오는 데이터를 이용해 데이터베이스에 추가
router.route('/process/adduser').post(function(req, res) {
    console.log('/process/adduser 호출됨.');
 
    var paramId = req.body.id || req.query.id;
    var paramPassword = req.body.password || req.query.password;
    var paramName = req.body.name || req.query.name;
    
    console.log('요청 파라미터 : ' + paramId + ', ' + paramPassword + ', ' + paramName);
    
    // 데이터베이스 객체가 초기화된 경우, addUser 함수 호출하여 사용자 추가
    if (database) {
        addUser(database, paramId, paramPassword, paramName, function(err, result) {
            if (err) {throw err;}
            
            // 결과 객체 확인하여 추가된 데이터 있으면 성공 응답 전송
            if (result && result.insertedCount > 0) {
                console.dir(result);
 
                res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
                res.write('<h2>사용자 추가 성공</h2>');
                res.end();
            } else {  // 결과 객체가 없으면 실패 응답 전송
                res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
                res.write('<h2>사용자 추가  실패</h2>');
                res.end();
            }
        });
    } else {  // 데이터베이스 객체가 초기화되지 않은 경우 실패 응답 전송
        res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
        res.write('<h2>데이터베이스 연결 실패</h2>');
        res.end();
    }
    
});
 
 
// 라우터 객체 등록
app.use('/', router);
 
 
// 사용자를 인증하는 함수
var authUser = function(database, id, password, callback) {
    console.log('authUser 호출됨 : ' + id + ', ' + password);
    
    // users 컬렉션 참조
    var users = database.collection('users');
 
    // 아이디와 비밀번호를 이용해 검색
    users.find({"id":id, "password":password}).toArray(function(err, docs) {
        if (err) { // 에러 발생 시 콜백 함수를 호출하면서 에러 객체 전달
            callback(err, null);
            return;
        }
        
        if (docs.length > 0) {  // 조회한 레코드가 있는 경우 콜백 함수를 호출하면서 조회 결과 전달
            console.log('아이디 [%s], 패스워드 [%s] 가 일치하는 사용자 찾음.', id, password);
            callback(null, docs);
        } else {  // 조회한 레코드가 없는 경우 콜백 함수를 호출하면서 null, null 전달
            console.log("일치하는 사용자를 찾지 못함.");
            callback(nullnull);
        }
    });
}
 
 
 
//사용자를 추가하는 함수
var addUser = function(database, id, password, name, callback) {
    console.log('addUser 호출됨 : ' + id + ', ' + password + ', ' + name);
    
    // users 컬렉션 참조
    var users = database.collection('users');
 
    // id, password, username을 이용해 사용자 추가
    users.insertMany([{"id":id, "password":password, "name":name}], function(err, result) {
        if (err) {  // 에러 발생 시 콜백 함수를 호출하면서 에러 객체 전달
            callback(err, null);
            return;
        }
        
        // 에러 아닌 경우, 콜백 함수를 호출하면서 결과 객체 전달
        if (result.insertedCount > 0) {
            console.log("사용자 레코드 추가됨 : " + result.insertedCount);
        } else {
            console.log("추가된 레코드가 없음.");
        }
        
        callback(null, result);
         
    });
}
 
 
// 404 에러 페이지 처리
var errorHandler = expressErrorHandler({
 static: {
   '404''./public/404.html'
 }
});
 
app.use( expressErrorHandler.httpError(404) );
app.use( errorHandler );
 
 
//===== 서버 시작 =====//
 
// 프로세스 종료 시에 데이터베이스 연결 해제
process.on('SIGTERM'function () {
    console.log("프로세스가 종료됩니다.");
    app.close();
});
 
app.on('close'function () {
    console.log("Express 서버 객체가 종료됩니다.");
    if (database) {
        database.close();
    }
});
 
// Express 서버 시작
http.createServer(app).listen(app.get('port'), function(){
  console.log('서버가 시작되었습니다. 포트 : ' + app.get('port'));
 
  // 데이터베이스 연결을 위한 함수 호출
  connectDB();
   
});
 
 
cs
책에서는 코드를 부분적으로 설명하느라 나눠서 그런건지 뒤죽박죽으로 되어있다.
예제를 홈페이지에서 받았는데 책과 다르다 ;;;; 위는 완성된 예제