티스토리 뷰

728x90
반응형

개요

마이크로서비스는 서비스간 호출 검증을 위해 Component / Contract Test를 시나리오를 정의하고, 파이프라인 또는 3rd Party 솔루션을 통해 자동 검증할 수 있도록 구성하는 것이 일반적이다. Component Test는 테스트를 수행하는 서비스를 기준으로 테스트 케이스를 정의하고, Contract Test는 서비스에서 제공하는 api를 호출하는 client 기준으로 테스트 케이스를 정의한다.

이와 같은 상호간의 인터페이스를 테스트하기 위해서는 각 서비스가 기동되어 있어야 하지만, 개발부서가 분리되어 있거나, client 입장에서 Contract를 테스트하기 위해서는 보다 번거로운 절차를 통해 테스트를 진행해야 한다.
물론 분산 트랜잭션 환경에서 당연히 거쳐야 하는 과정이지만, 마이크로서비스 환경에서는 서비스가 세분화 되어 있기 때문에 보다 복잡하게 얽혀있고, 서비스간 계약 관계를 관리하고 유지하는데 어려움이 따른다.

위와 같이 MicroService B가 특정 이유(서비스가 중지되었거나, 서비스 개발팀이 휴가를 갔거나, 네트워크가 차단되었거나 등 수많은 이유)로 인해 접속이 불가능한 경우 MicroService A와 C는 서비스 테스트가 불가능하고, 테스트 자동화 구성이 되어 있는 환경에서는 타 서비스가 기동될 때까지 CI 실패가 발생할 수도 있다.

이를 해소하기 위해 위와 같이 MockServer를 구성하여 각 로컬 서비스 내에 테스트가 가능하도록 구현해 두는 것을 권고한다. 각 서비스에서 제공하는 api를 가상의 서버인 MockServer에 정의하고, 가상의 데이터를 리턴해 주는 형태로 구성한다. 이때 MockServer는 API 규격에 맞는 데이터를 리턴타입에 맞게 임시 데이터를 생성하고 리턴한다. 이를 통해 서비스간 API 규격을 공유할 수 있는 환경만 명확하게 제공된다면, 서비스 간 Component/Contract 테스트는 로컬 서비스 내에서 수행할 수 있도록 구성할 수 있다. MockServer는 단일 또는 서비스별로 구분하여 구성할 수 있다.

이번 포스팅에서는 OpenSource MockServer인 Prism에 대해 알아보도록 하자.

# 참고 URL : https://stoplight.io/open-source/prism/


Prism 설치

Prism은 Node 기반으로 설치된다. nvm > node > npm 순으로 설치를 진행한다.

1) nvm 설치

[root@ip-192-168-84-159 ~]# curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 13226  100 13226    0     0  99443      0 --:--:-- --:--:-- --:--:-- 99443
=> Downloading nvm from git to '/root/.nvm'
=> Cloning into '/root/.nvm'...
remote: Enumerating objects: 278, done.
remote: Counting objects: 100% (278/278), done.
remote: Compressing objects: 100% (245/245), done.
remote: Total 278 (delta 31), reused 101 (delta 20), pack-reused 0
Receiving objects: 100% (278/278), 142.25 KiB | 813.00 KiB/s, done.
Resolving deltas: 100% (31/31), done.
=> Compressing and cleaning up git repository

=> Appending nvm source string to /root/.bashrc
=> Appending bash_completion source string to /root/.bashrc
=> Close and reopen your terminal to start using nvm or run the following to use it now:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion
[root@ip-192-168-84-159 ~]# . ~/.nvm/nvm.sh

2) node 설치

[root@ip-192-168-84-159 ~]# nvm install node
Downloading and installing node v17.3.0...
Downloading https://nodejs.org/dist/v17.3.0/node-v17.3.0-linux-x64.tar.xz...
##################################################################################################################################################################################################################################### 100.0%
Computing checksum with sha256sum
Checksums matched!
Now using node v17.3.0 (npm v8.3.0)
Creating default alias: default -> node (-> v17.3.0 *)
[root@ip-192-168-84-159 ~]#

3) prism 설치

[root@ip-192-168-84-159 ~]# npm install -g @stoplight/prism-cli
npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142

added 239 packages, and audited 240 packages in 22s

11 packages are looking for funding
  run `npm fund` for details

11 moderate severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.
[root@ip-192-168-84-159 ~]#

Prism 적용

api contract로 등록할 대상 서비스에 대해 다음과 같은 절차를 진행한다.

  • swagger api document 다운로드
  • mock server(prism) 기동
  • api test
  • mock server 로그 및 리턴 결과 확인

1) swagger api doc download

http://[IP]:[PORT]/[CONTEXT]/v2/api-docs로 접근할 경우 api doc을 확인할 수 있다. 이를 아래와 같이 저장하여 mock server에 적용한다.

[root@ip-192-168-84-159 ~]# cat swagger-prism.json 
{
   "swagger":"2.0",
   "info":{
      "description":"Api Documentation",
      "version":"1.0",
      "title":"Api Documentation",
      "termsOfService":"urn:tos",
      "contact":{
         
      },
      "license":{
         "name":"Apache 2.0",
         "url":"http://www.apache.org/licenses/LICENSE-2.0"
      }
   },
   "host":"localhost:8075",
   "basePath":"/prism/account",
   "tags":[
      {
         "name":"account-controller",
         "description":"Account Controller"
      }
   ],
   "paths":{
      "/prism/rest/v0.8/{acntNo}":{
         "get":{
            "tags":[
               "account-controller"
            ],
            "operationId":"retrieveAccountNoUsingGET",
            "consumes":[
               "application/json",
               "application/xml",
               "text/xml",
               "text/html"
            ],
            "produces":[
               "application/json",
               "application/xml",
               "text/xml",
               "text/html"
            ],
            "parameters":[
               {
                  "name":"acntNo",
                  "in":"path",
                  "description":"acntNo",
                  "required":true,
                  "type":"string"
               }
            ],
            "responses":{
               "200":{
                  "description":"OK",
                  "schema":{
                     "type":"integer",
                     "format":"int64"
                  }
               },
               "401":{
                  "description":"Unauthorized"
               },
               "403":{
                  "description":"Forbidden"
               },
               "404":{
                  "description":"Not Found"
               }
            }
         }
      },
      "/prism/rest/v1.8/{acntNo}":{
         "get":{
            "tags":[
               "account-controller"
            ],
            "operationId":"retrieveTransactionAccountNoUsingGET",
            "consumes":[
               "application/json",
               "application/xml",
               "text/xml",
               "text/html"
            ],
            "produces":[
               "application/json",
               "application/xml",
               "text/xml",
               "text/html"
            ],
            "parameters":[
               {
                  "name":"acntNo",
                  "in":"path",
                  "description":"acntNo",
                  "required":true,
                  "type":"string"
               }
            ],
            "responses":{
               "200":{
                  "description":"OK",
                  "schema":{
                     "type":"array",
                     "items":{
                        "$ref":"#/definitions/TransactionHistory"
                     }
                  }
               },
               "401":{
                  "description":"Unauthorized"
               },
               "403":{
                  "description":"Forbidden"
               },
               "404":{
                  "description":"Not Found"
               }
            }
         }
      }
   },
   "definitions":{
      "TransactionHistory":{
         "type":"object",
         "properties":{
            "acntBlnc":{
               "type":"integer",
               "format":"int64"
            },
            "acntNo":{
               "type":"string"
            },
            "divCd":{
               "type":"string"
            },
            "seq":{
               "type":"integer",
               "format":"int32"
            },
            "stsCd":{
               "type":"string"
            },
            "trnsAmt":{
               "type":"integer",
               "format":"int64"
            },
            "trnsBrnch":{
               "type":"string"
            },
            "trnsDtm":{
               "type":"string"
            }
         }
      }
   }
}
[root@ip-192-168-84-159 ~]#

2) prism 기동 (prism mock --host [IP] -p [PORT] -d [api-doc.json]

[root@ip-192-168-84-159 ~]# prism mock --host 0.0.0.0 -p 15002 -d swagger-prism.json
[2:29:03 PM] [39m [CLI]  awaiting  Starting Prism
[2:29:04 PM] [39m [CLI]  info      GET        http://0.0.0.0:15002/prism/rest/v0.8/architecto
[2:29:04 PM] [39m [CLI]  info      GET        http://0.0.0.0:15002/prism/rest/v1.8/eligendi
[2:29:04 PM] [39m [CLI]  start     Prism is listening on http://0.0.0.0:15002

3) API 단위 테스트

4) prism 로그 확인

[root@ip-192-168-84-159 ~]# prism mock --host 0.0.0.0 -p 15002 -d swagger-prism.json
[2:29:03 PM] [39m [CLI]  awaiting  Starting Prism
[2:29:04 PM] [39m [CLI]  info      GET        http://0.0.0.0:15002/prism/rest/v0.8/architecto
[2:29:04 PM] [39m [CLI]  info      GET        http://0.0.0.0:15002/prism/rest/v1.8/eligendi
[2:29:04 PM] [39m [CLI]  start     Prism is listening on http://0.0.0.0:15002
[2:30:48 PM] [39m [HTTP SERVER] get /prism/rest/v0.8/architecto  info      Request received
[2:30:48 PM] [39m     [NEGOTIATOR]  info      Request contains an accept header: */*
[2:30:48 PM] [39m     [VALIDATOR]  success   The request passed the validation rules. Looking for the best response
[2:30:48 PM] [39m     [NEGOTIATOR]  success   Found a compatible content for */*
[2:30:48 PM] [39m     [NEGOTIATOR]  success   Responding with the requested status code 200
[2:31:39 PM] [39m [HTTP SERVER] get /prism/rest/v1.8/eligendi  info      Request received
[2:31:39 PM] [39m     [NEGOTIATOR]  info      Request contains an accept header: */*
[2:31:39 PM] [39m     [VALIDATOR]  success   The request passed the validation rules. Looking for the best response
[2:31:39 PM] [39m     [NEGOTIATOR]  success   Found a compatible content for */*
[2:31:39 PM] [39m     [NEGOTIATOR]  success   Responding with the requested status code 200

# 트러블슈팅

CASE 1 : NO_COMPLEX_OBJECT_TEXT (500 Error)

아래와 같이 produces를 정의한다.

[swagger-prism.json]
...

            "consumes":[
               "application/json"
            ],
            "produces":[
               "application/json",
               "application/xml",
               "text/xml",
               "text/html"
            ],
...

재호출 시 정상 호출되는 것을 확인할 수 있다.

CASE 2 NO_PATH_MATCHED_ERROR (404 Client Error)

Contex 정보 및 도메인 정보 입력 실패

[root@ip-192-168-84-159 ~]# prism mock --host 0.0.0.0 -p 15002 -d swagger-prism.json
[2:56:13 PM] [39m [CLI]  awaiting  Starting Prism
[2:56:13 PM] [39m [CLI]  info      GET        http://0.0.0.0:15002/prism/rest/v0.8/sapiente
[2:56:13 PM] [39m [CLI]  info      GET        http://0.0.0.0:15002/prism/rest/v1.8/voluptatum
[2:56:13 PM] [39m [CLI]  start     Prism is listening on http://0.0.0.0:15002
[2:56:17 PM] [39m [HTTP SERVER] get /hellomynameis/prism/rest/v1.8/architecto  info      Request received
[2:56:17 PM] [39m [HTTP SERVER] get /hellomynameis/prism/rest/v1.8/architecto  error     Request terminated with error: https://stoplight.io/prism/errors#NO_PATH_MATCHED_ERROR: Route not resolved, no path matched

기동 시 호출 URL 정보 확인 후 호출을 진행한다.


결론

이와 같이 Prism을 활용하면, 분산 서비스 환경에서 상호간 Contract를 관리하는 mockserver로써의 역할 수행할 수 있다.

그 밖에 Prism은 로드된 문서의 변경 사항을 자동으로 감지하여 서버를 다시 시작시켜 변경 사항을 반영한다. 즉 수정된 api document를 특정 위치에 업로드만 하면, 손쉽게 prism 서버에 반영할 수 있다는 의미이다. 이를 활용하면, 자동화 체계를 설계할 수 있다. 예를 들어 api portal을 통해 api가 추가될 경우 이와 동시에 swagger json을 prism 서버에 업로드 하는 기능을 만든다고 할때, 손쉽게 MockServer 환경을 관리할 수 있을 것이다. 또한, Proxy 서버의 역할로도 활용할 수 있다.

Prism은 누구나 손쉽게 구성이 가능한 Mockserver로 각 개발환경에 적용해 보고 API Portal과 함께 활용 방안에 대해 프로젝트 별로 고민해 보도록 하자.

728x90
반응형