그래 그리 쉽지는 않겠지

검색

검색 아이콘검색을 여는 아이콘

Tucker의 Go 언어 프로그래밍 Ch27 ~ Ch30

2023-11-05

이 글은 골든래빗 《 Tucker의 Go언어 프로그래밍 》의 27장~30장 써머리입니다.

Ch27 ~ Ch30

  • 객체지향 설계 원칙 SOLID
  • 테스트와 벤치마크
  • Go로 만드는 웹서버
  • RESTful API 서버 만들기

# 객체지향 설계 원칙 SOLID

좋은 설계를 위해 결합도를 낮추고 응집도를 높일 수 있는 원칙

  • 결합도 ⬇️
    • 모듈이 서로 강하게 결합되어 있어 떼어낼 수 없는 정도
  • 응집도 ⬆️
    • 하나의 모듈이 스스로 자립할 수 있는 정도

# 단일 책임 원칙

“모든 객체는 책임을 하나만 져야 한다.”

  • 객체나 모듈의 변경의 이유는 단 하나여야 한다

# 개방-패쇄 원칙

“확장에는 열려 있고, 변경에는 닫혀 있다.”

  • 기능 추가시 기존 코드의 변경을 최소화해야 한다

# 리스코프 치환 원칙

“q(x)가 타입 T의 객체 x에 대해 증명할 수 있는 속성이라면 q(y)는 T의 하위 타입 S의 객체 y에 대해 증명할 수 있어야 한다.”

함수 계약 관계를 준수해야 함

  • 구현체는 호출자의 의도(예상되는 동작)에 맞게 동작해야함

상속은 리스코프 치환 원칙을 위배하기 쉽다

  • 부모 클래스의 메서드와 자식 클래스의 오버라이딩한 메서드가 서로 다른 동작이 가능

e.g. 이미지의 가로 크기를 늘리는 함수 (인자: Rectangle 타입)

  • 이미지 객체는 Rectangle 타입(인터페이스) 이며 setWidth 메서드를 가지고 있음
  • 이미지 객체의 setWidth 를 호출
  • ⚠️ 문제 발생
    • Rectangle 타입을 상속(구현)한 Square 타입이 매개변수로 넘어옴
    • Square 타입의 setWidth 는 width와 height 를 모두 변경
    • 가로 크기만 늘리려 했지만 세로 크기도 늘어남 (‼️ 예상치 못한 동작 발생)

# 인터페이스 분리 원칙

“클라이언트는 자신이 이용하지 않는 메서드에 의존하지 않아야 한다.”

  • 인터페이스를 분리하여 불필요한 메서드들과의 의존 관계가 없어야 한다

# 의존 관계 역전 원칙

“상위 계층이 하위 계층에 의존하는 전통적인 의존 관계를 역전시킴으로써 상위 계층이 하위 계층의 구현으로부터 독립되게 할 수 있다.”

  • 구체화된 객체는 추상화된 객체와 의존 관계를 가져야 한다

원칙

  • 상위, 하위 모듈 모두 추상 모듈에 의존
  • 추상 모듈은 구체화된 모듈에 의존해서는 안 된다. 구체화된 모듈은 추상 모듈에 의존해야 한다

이점

  • 결합도가 낮아져 이식성이 증가
  • 서로 독립성이 유지

# 테스트와 벤치마크

# 테스트 코드

1
2
3
go test

go test -run 테스트명 # 일부 테스트만 실행
  • 파일명이 _text.go로 끝나야 함
  • testing 패키지를 가져와야 함
  • 함수명은 Test로 시작해야 함
    • 매개변수는 t *test.T 하나만 존재해야 함

stretchr/testify

  • assert 객체
    • 테스트 코드를 쉽게 만들 수 있는 다양한 메서드 포함
      • Equal(), Greater(), Len(), NotNilf(), NotEqualf()
  • mock 패키지
    • 모듈의 행동을 가장하는 목업 객체 제공
  • suite 패키지
    • 테스트 준비 작업이나 종료 후 뒤처리 작업을 쉽게 할 수 있도록 도와줌

테스트 주도 개발

테스트 작성 -> 테스트 실패 -> 코드 작성 -> 테스트 성공 -> 개선(SOLID 원칙 입각)

  • 위의 과정을 반복

# 벤치마크 코드

코드 성능을 측정

1
go test -bench .
  • func BenchmarkXxxx(b *testing.B) 의 형태를 가져야 함

# Go로 만드는 웹서버

# 웹서버

  1. 핸들러 등록
    • http.HandleFunc() -> DefaultServeMux 에 등록
    • http.NewServeMux()
      • ServeMux 객체 생성
      • 생성한 ServeMux 객체의 HandleFunc() 를 호출하여 등록
  2. 웹서버 시작
    • http.ListenAndServe(addr string, handler http.Handler)
      • addr
        • 포트번호
      • handler
        • http.Handler 의 구현체인 *http.ServeMux 사용
        • nil 일 경우 내부적으로 DefaultServeMux 를 사용

구조

tucker-golang-programming-ch29.png

# 파일서버

1
2
3
4
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))  
if err := http.ListenAndServe(":3000", nil); err != nil {  
    panic(err)
}
  • “/static” 안의 요청 URL.Path 에 해당하는 파일을 서빙
    • http.StripPrefix 로 요청 URL.Path 에서 “/static/” 제거

# 테스트 코드

1
2
3
// ResponseRecorder를 반환
httptest.NewRecorder() // ResponseRecorder: 나중에 테스트에서 검사할 수 있도록 변형을 기록하는 http.ResponseWriter의 구현
httptest.NewRequest("GET", "/", nil) // 새 수신 서버 요청을 반환

# JSON 데이터 전송

Encoder, Decoder

  • Encoder
    • io.Writer 를 가짐
  • Decoder
    • io.Reader 를 가짐
  • 한 번에 저장하는 대신 스트림 데이터를 인코딩, 디코딩

Marshal, Unmarshal

  • Marshal
    • any 타입의 값을 받아와 byte 슬라이스로 변환
  • Unmarshal
    • byte 슬라이스를 받아와 파싱한 결과를 any 타입(주소값)에 저장
  • 모든 데이터를 byte 슬라이스에 저장하기 때문에 메모리 집약적

참조: https://blog.devgenius.io/to-unmarshal-or-to-decode-json-processing-in-go-explained-e92fab5b648f

# HTTPS

공개키 암호화 방식 사용

참조: https://ko.khanacademy.org/computing/computer-science/cryptography/modern-crypt/v/diffie-hellman-key-exchange-part-1

외부 공인 기관의 인증

피싱 사이트의 경우 해커가 공개키를 만들어 자신의 비밀키로 복호화할 수 있음

  • 인증기관이 웹사이트를 인증 후 웹사이트의 공개키를 암호화한 공개키를 발급 (인증서에 포함)

키 생성 및 인증서 발급

1
2
3
4
5
# localhost.key: 비밀키, localhost.csr: 인증파일
openssl req -new -newkey rsa:2048 -nodes -keyout localhost.key -out localhost.csr

# 셀프 인증서 발급
openssl x509 -req -days 365 -in localhost.csr -signkey localhost.key -out localhost.crt

https 서버 실행

1
http.ListenAndServeTLS(":3000", "localhost.crt", "localhost.key", nil)

# RESTful API 서버 만들기

REST (Representational State Transfer)

  • URL과 메서드로 데이터와 동작을 표현하는 방식

특징

  • URL과 메서드 조합으로 어떤 데이터에 대한 어떤 동작인지 정의
  • 서버(백엔드)는 데이터 제공자로 데이터만 제공하고 클라이언트(프론트)에서 데이터를 처리하고 화면에 표시
  • 무상태
    • 서버는 클라이언트의 상태를 유지 하지 않음
  • 캐시 처리
    • 서버가 단순해져서 더 쉽게 캐시 정책 적용이 가능

코드

1
2
3
4
5
router := mux.NewRouter()  
router.HandleFunc("/students", GetStudentListHandler).Methods("GET")  
router.HandleFunc("/students/{id:[0-9]+}", GetStudentHandler).Methods("GET")  
router.HandleFunc("/students", PostStudentHandler).Methods("POST")  
router.HandleFunc("/students/{id:[0-9]+}", DeleteStudentHandler).Methods("DELETE")

# References