요구사항과 시나리오

시나리오

<도넛-스테이츠>는 온라인으로 도넛을 판매합니다. 웹사이트를 통해서 주문 버튼을 누르는 것으로 구매(Sales API)가 가능합니다. 창고에 재고가 있다면 재고가 감소하고 구매가 완료됩니다. 유튜브스타 hoyong.LEE가 도넛-스테이츠의 도넛이 맛있다고 영상을 올렸습니다. 그를 따르는 데브옵스 수강생들이 몰려듭니다. 주문이 급등합니다. 창고에 재고가 없기 때문에 구매가 불가능한 경우가 발생합니다. 창고의 도넛의 재고가 다 떨어지면 제조 공장에 알려서 다시 창고를 채우는 시스템을 구축해야 합니다. 제조 공장인 <팩토리-스테이츠>에 주문을 요청(Leagcy Factory API)할 수 있습니다. 주문이 요청되면 일정 시간이 지난 후 창고에 재고가 증가합니다.

상황

비효율적인 레거시 시스템 때문에 고객의 불만사항이 접수되고 있습니다. 제품별 재고부족 요청이 빈번하게 발생되고 있지만 전달 과정에서 지연과 누락 등 문제 상황이 발생하고 있습니다. 안정적으로 요청이 전달 될 수 있도록 시스템을 개선해야합니다. 비정상적으로 처리된 요청의 경우 운영팀에 상황을 알려야합니다.



요구사항 1 : 재고부족으로 인한 구매실패에 대한 조치

  1. Sales API 를 통해 요청을 받은 서버가 데이터베이스에서 재고 상황을 확인합니다.
  2. 재고가 있다면 감소시키고 응답으로 판매완료 내용을 전달합니다.
  3. 재고가 없는 경우 공장에 주문을 진행합니다
  4. 재고가 없다는 내용을 담은 메세지 페이로드와 함께 SNS topic이 생성됩니다.
  5. 메시지가 SQS로 구현된 stock_queue에 수신됩니다.

 

요구사항 2 : 메시지 누락 상황에 대한 조치

  1. 빈번한 요청으로 메시지 누락이 발생합니다.
  2. SQS에서 처리완료되지 않은 메시지들을 체계적으로 관리할 dead_letter_queue를 생성해야합니다.
  3. stock_queuedead_letter_queue를 연결합니다.

 

요구사항 3 : Legacy 시스템(Factory → Warehouse) 성능문제에 대한 조치

  1. 안정적으로 이벤트가 전달 될 수 있는 시스템을 구축해야합니다.
  2. stock_queue 의 메시지를 소비하는 stock_lambda에 의해 Factory API가 호출됩니다.
  3. Factory의 생산 완료 메시지를 수신한 stock_inc_lambdaDB에 상품 재고를 증가시킵니다.

Day1 : 리소스 다이어그램

작성한 리소스 다이어그램

먼저 USER가 sales api로 도넛 구매요청을 한다.

 만약, 도넛의 남은 재고가 없다면, SNS를 통해서 SQS로 "재고가 없어용 채워야해용" 메시지를 전송하고 SQS에 새 메시지가 들어오는 것을 트리거로 작동하는 Lambda함수를 통해 도넛공장 api로 도넛을 더 구워달라는 요청을 보낸다.

 그리고 도넛공장 api에서 제빵 완료라는 응답을 받은 Lambda는 DB의 남은 도넛의 재고를 증가시키는 매커니즘이다.

 


 

Day2 : 구현

 

 만들어둔 sales API와 재고 DB를 환경변수로 연결 정보를 설정해서 서로 연결해 DB연결이 잘 되는지 확인한다. 그렇게 문제가 없다고 생각해서 SALES API를 Lambda로 배포하고 SNS, SQS를 만들어준다. 그리고 재고부족요청을 POSTMAN으로 보냈는데 SNS로 메시지가 넘어가지 않는다. 그래서 CloudWatch 로그를 뒤적여보니 SNS 권한이 없단다. 그래서 먼저 SNSFullAccess 권한을 Lambda에 부여해주고 테스트를 한다음, 더 작은 권한인 SNS publish 권한을 부여했다. 

 

 그리고 SQS를 트리거로 하는 stock-lambda 람다가 SQS에 들어온 메시지를 가져가서 소비하는지 확인해봤다. 그런데 이번에도 권한이 없다는 메시지를 받아서 해당 Lambda함수의 역할에 SQS메시지 실행권한을 넣어줬더니 아주 잘 작동하는 것을 확인할 수 있었다.

 

 마지막으로 콘솔에서 했던 SNS, SQS 작업들을 serverless에서도 할 수 있도록 serverless.yaml 파일을 작성해줬다. 

service: sales-api
frameworkVersion: '3'

provider:
  name: aws
  runtime: nodejs14.x
  region: ap-northeast-2 #리전 설정.(default: us-east-1)
  iam: #iam으로 부여할 권한을 설정해준다.
    role:
      statements:
        - Effect: 'Allow'
          Action:
            - 'SNS:Publish' 
          Resource: !Ref stockTopic
  timeout: 15

constructs:
  stockQueue:
    type: queue
    worker:
      handler: stock.handler #핸들러 매핑시켜주기

functions:
  api:
    handler: api.handler
    events:
      - httpApi: '*'


resources:
  Resources:
    stockTopic: #SNS 토픽 설정
      Type: AWS::SNS::Topic
      Properties:
        TopicName: stock_empty
    stockSub: #SNS 토픽 구독 설정
      Type: AWS::SNS::Subscription
      Properties:
        TopicArn: !Ref stockTopic
        Endpoint: ${construct:stockQueue.queueArn}#큐의 ARN
        Protocol: sqs
        RawMessageDelivery: true

    stockQueuePolicy: #SQS에 접근할 수 있도록 권한을 부여한다.
      Type: AWS::SQS::QueuePolicy
      Properties:
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                Service: sns.amazonaws.com
              Action: 'sqs:SendMessage'
              Resource: ${construct:stockQueue.queueArn}
              Condition:
                ArnEquals:
                  aws:SourceArn: !Ref stockTopic
        Queues:
          - ${construct:stockQueue.queueUrl} #해당 큐의 URL

plugins:
  - serverless-lift

 


Day3 : 구현 및 추가 요구사항에 대한 다이어그램 작성

받아온 Stock empty 함수에서 Factory API에 원할하게 도넛생산요청을 보내고 Factory API의 응답에 따라서 stock increase function이 재고DB에 재고를 추가한다. 아래는 간단하게 작성해본 stock empty 함수 배포용 serverless.yaml이다. js코드만 잘 작성해주니 오류없이 편안하게 작동한다 ㅎㅎㅎ

service: stock-lambda
frameworkVersion: '3'

provider:
  name: aws
  runtime: nodejs14.x
  region: ap-northeast-2

functions:
  consumer:
    handler: index.consumer
    events:
      - sqs: arn:aws:sqs:ap-northeast-2:비밀이에요 ㅎㅎ:stock_empty

plugins:
  - serverless-lift

추가 요구사항

  • 추가 시나리오에 대한 아키텍처를 구성해야 합니다.
    • 실제 구현하지 않아도 아키텍처상에 리소스 추가 배치
  • 확장된 아키텍처를 가지고 팀의 의견을 제시할 수 있어야 합니다.

a. 광고 중단 요청 진행 시나리오

재고가 없는 상황에서도 광고가 계속 진행되고 있습니다. 광고 비용 절감과 고객불만을 낮추기 위한 조치가 필요합니다. 메시지가 유실되는 상황을 막기 위해 내구성을 갖춘 시스템이 필요합니다.

  • 요구사항
    • 재고를 채우기 위한 과정이 진행될 때 광고 담당자에게 광고 중단 요청 내용을 담은 이메일이 전송되어야 합니다.
    • 메시지에 대한 내구성을 강화하기 위해 메시지 Queue가 사용되어야 합니다.
    • AWS SES 서비스를 이용해서 이메일을 전송해야 합니다.

b. VIP 고객관리 프로세스 추가 시나리오

모니터링 결과 대량 주문을 하는 일부 고객들이 확인되었습니다. 대량 구매 고객들의 사용자 정보를 식별할 수 있어야 합니다. 고객정보는 별도의 서버(EC2)와 데이터베이스(RDS)에서 관리되고 있습니다. 데이터베이스 기록과 외부 마케팅 시스템으로의 연결과정의 오류를 대비하기 위한 내구성 갖춘 시스템이 필요합니다.

  • 요구사항
    • 100개 이상 구매가 발생 시 해당 유저의 타입이 normal에서 Vip로 변경되어야 합니다.
    • 메시지에 대한 내구성을 강화하기 위해 메시지 Queue가 사용되어야 합니다.
    • 고객관리는 별도의 데이터베이스(RDS)로 관리되고 있기 때문에 해당 데이터베이스에 접근해서 정보를 수정해야 합니다.

추가 요구사항 반영한 다이어그램

추가 시나리오  a. 광고중단

재고주문알림의 메시지에 광고주에 대한 정보도 담아서 SQS에 메시지를 전달하면 해당 메시지를 받은  Stock Lambda가 메시지를 처리해 SES서비스를 통해 광고주에게로 광고중단메일을 송신한다.

추가 시나리오  b. 고객관리

먼저 100개 이상 주문하는 고객이 생기면 sales api에서 sns를 통해 VIP 고객관리 태그에 속한 SQS로 메시지를 보낸다. SQS를 트리거로 하는 Lambda가 해당 메시지를 받아서 EC2로 넘기게 된다. 그렇게 되면 EC2는 해당 메시지를 처리해 RDS에 담겨있는 고객 정보를 수정한다.

여기서 어째서 컴퓨팅 리소스를 EC2와 람다 2개를 같이 붙여서 사용하는지에 대한 의문이 들 수 있다. SQS에서 EC2가 메시지를 곧바로 받아오기 위해선 SQS를 풀링하는 코드를 실시간으로 실행해야하는데, Lambda의 트리거 기능을 사용하면 EC2에서 계속 풀링할 필요가 없어지기 때문에 EC2의 리소스를 절약하기 위해서 Lambda를 사용했다.

 

 


마치며..

이번 프로젝트는 내가 어떤 작업을 해야할지 명확하게 파악할 수 있도록 가이드라인을 만들어서 진행과정 중에 혼선이 적었다. 역시 프로젝트 초기 단계에서 방향성을 명확하게 하는 것이 아주 중요하다는 것을 다시한번 마음에 세길 수 있었고, IaC작업은 여전히 신기하고 흥미롭다 ㅎㅎㅎ. 아직은 리소스 하나를 작성하는데에도 많은 문서를 뒤적여야하는 응애이지만, 잘 쓸 수만 있다면 손쉽게 배포를 자동화시킬 수 있는 해피한 엔지니어가 될 수 있을 거라 믿는다.

 


도메인은 지식, 영향력 또는 활동 영역을 말하며, 개발 분야에서는 소프트웨어로 해결하려는 문제의 영역을 의미한다.

도메인을 표현하는 대표적인 사례 : 객체 지향 프로그래밍

  • 객체 지향 프로그래밍에서는 도메인이 클래스(상태, 동작을 가지는 객체)로 표현된다.
  • 어떤 도메인을 개념적으로 표현하는 방법 : 객체 지향 설계, ERD 등

도메인 주도 설계 (Donaim Driven Design)

  • 도메인 내부에서 문제 해결이 가능한 형태로 도메인을 모델링 하는 기법.
  • 하나의 도메인 모델에 대한 이해관계가 각자 다름을 인정하고,
  • 각 팀에 적합한 하위 도메인을 설정하고,
  • 해당 하위 도메인에 대한 맥락을 알고 있는 사람이 따라야 할 비즈니스 규칙에 대한 경계를 설정하는 설계 방식이다.
  • 따라서 마이크로서비스와 궁합이 좋다.

보편 언어 (ubiquitous language)

  • 도메인의 특정 업무와 관련된 사람들 사이에서 통용되는 개념
  • 페이먼츠 팀 : 고객이 '결제' 했어요
  • 배달 팀 : '배달' 건수가 생겼어요
  • 가맹점 팀 : '주문서'가 도착했어요

한정된 맥락 (bounded context)

  • 요구사항을 모놀리틱(단일 프로그램) 아키텍처로 구성하면 클래스 이름 정의하기도 쉽지 않다.
  • 이름을 지었다 한들 클래스가 매우 비대해질 우려가 있다. ("클래스는 하나의 책임만 가져야 한다"는 단일 책임 원칙 위배)
  • 다른 목적을 갖도록 클래스를 분리했다 하더라도, 의존 관계를 신경써야 한다.
  • 모델의 경계를 분명하게 구분짓고, 업무 범위 내로만 아키텍처를 구성하면 된다.
  • 업무 범위를 나누어 타당한 클래스 이름 짓기가 가능해지고, 모든 도메인에 용어를 맞추기 위해 모호한 단어를 쓸 필요가 없다.

DDD를 실천하기에 딱 맞는 마이크로서비스 아키텍처를 도입하려면,

  1. 도메인 지식을 가진 엔지니어가 팀 마다 있어야 자연스럽게 기능 조직이 아니라 목적 조직화 된다.
  2. 여러 서비스가 잘 결합할 수 있게 디자인해야 한다. 하지만, 동시에 다른 서비스에 지나치게 의존해서도 안된다.
  3. 서비스를 오케스트레이션 해야한다. 오케스트레이터는 각 서비스가 죽는지 안죽는지 여부, 트래픽이 수용 가능한지 아닌지 여부, 인프라 유지보수 (업그레이드, 패치 등) 업무 등에만 집중한다.

따라서 마이크로서비스로 소프트웨어를 작성할 때에는, 소프트웨어 작성에 앞서 팀의 일하는 방식을 보다 독립적으로 만들어내야 한다. 반대로 마이크로서비스 아키텍처를 통해, 팀의 일하는 방식이 보다 독립적으로 만들어질 수 있다. 즉, 문화로서의 DevOps를 실천해야만 가능하다.

 

DDD 주요 용어


  • 도메인 이벤트: 발생한 사건
  • 커맨드: 도메인 이벤트를 트리거하는 명령
  • 외부 시스템: 도메인 이벤트가 호출하거나 관계가 있는 레거시 또는 외부 시스템
  • 액터: 개인 또는 조직의 역할
  • 핫스팟: 의문사항, 결정하기 힘든 사항
  • 애그리거트: 도메인 이벤트와 커맨드가 처리하는 데이터, 상태가 변경되는 데이터
  • 정책: 이벤트 조건에 따라 진행되는 결정, “이벤트”가 발생할 때, “커맨드”를 실행한다
  • 정보: 액터에게 제공되는 데이터, 결정을 내리는데 영향을 주는 정보

DDD 제작 순서

  1. 도메인 이벤트 찾기
  2. 외부 시스템/외부
  3. 프로세스 찾기
  4. 커맨드 찾기
  5. 액터 찾기
  6. 애그리거트 정의
  7. 바운디드 컨텍스트 정의
  8. 컨텍스트 매핑

예시로 DDD를 만들어 보았다.

중앙방역대책본부에는 다양한 팀(DDD의 관점에서는 Actor)이 있지만, 대중에게 가장 잘 알려진 다음의 네 팀의 업무만을 이해해 보도록 하자.

 

  •  


 


 


 



miro를 사용해 다이어그램 형식으로 작성했다.

프로젝트 2를 진행하면서 해결해야할 여러 문제상황들을 마주했다.

그래서 주요 문제상황과 그 상황을 어떻게 해결했는지 기록했다.


ISSUE SOLUTION LOG

📝 문제 1 : 아마존 ECR IAM ROLE 생성

  1. IAM 자격 증명 공급자 리소스를 생성

https://blog.kakaocdn.net/dn/2mAFv/btsdi2512RZ/B0MzOft5l5iFMriNbkjbYK/img.png

  1. 웹 자격 증명 또는 OIDC를 위한 역할 생성

https://blog.kakaocdn.net/dn/C9iJ5/btsdlJSEoG1/9zhjPgGCnIjVvgiirVkkd1/img.png

  1. 탐색 창에서 역할(Roles)을 선택한 후 역할 생성(Create role)을 선택
  2. 웹 ID(Web Identity) 역할 유형을 선택
  3. 자격 증명 공급자(Identity provider)에서 역할의 IdP를 선택
  4. Audience에서 지정한 자격증명 공급자의 대상 선택
  5. 권한 정책을 사용하기 위한 정책을 선택하거나 정책 생성(Create policy)을 선택하여 새 브라우저 탭을 열고 완전히 새로운 정책을 생성할 수 있음(자세한 내용은 IAM 정책 생성 섹션을 참조)
    • 웹 ID 사용자에게 부여하려는 권한 정책 옆의 확인란을 선택 원할 경우, 여기서 정책을 선택하지 않고 나중에 정책을 만들어서 역할에 연결할 수 있음 (기본적으로 역할은 권한이 없음)
  6. 역할 이름(Role name)에 역할 이름을 입력
    • 역할 이름은 AWS 계정 내에서 고유해야하며 대소문자를 구분하지 않음
    • 다른 AWS 리소스가 역할을 참조할 수 있기 때문에 역할을 생성한 후에는 역할 이름을 편집할 수 없음
  7. 역할에 대한 사용 사례와 권한을 편집하려면 1단계: 신뢰할 수 있는 엔터티 선택(Step 1: Select trusted entities) 또는 2단계: 권한 추가(Step 2: Add permissions) 섹션에서 편집(Edit)을 선택
  8. 역할을 검토한 다음 [Create role]을 선택
  9. GitHub OIDC ID 제공자의 역할 구성
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": { # OIDC Role ARN
                "Federated": "arn:aws:iam::123456123456:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:<GitHub 조직명>/<레파지토리명>:*"
                },
                "StringEquals": {
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                }
            }
        }
    ]
}
클레임 클레임 유형 설명
aud 사용자 - 기본적으로 리포지토리를 소유하는 organization 같은 리포지토리 소유자의 URL- 사용자 지정할 수 있는 유일한 클레임도구 키트 명령- https://www.npmjs.com/package/@actions/core/v/1.6.0을 사용하여 사용자 지정 대상 그룹을 설정 가능
iss 발급자 -OIDC 토큰 발급자: https://token.actions.githubusercontent.com/
sub 제목 - 클라우드 공급자가 유효성을 검사할 주체 클레임을 정의- 이 설정은 액세스 토큰이 예측 가능한 방식으로만 할당되도록 하는 데 필수
  1. ECR 로그인을 위한 권한 부여

4-1. ECR Private에 로그인하기 위한 최소 권한 집합

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "GetAuthorizationToken",
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken"
      ],
      "Resource": "*"
    }
  ]
}

4-2. ECR 프라이빗 리포지토리에서 이미지를 가져오기 위한 최소 권한

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowPull",
      "Effect": "Allow",
      "Action": [
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer"
      ],
      "Resource": "arn:aws:ecr:<리전>:<AWS 계정 ID>:repository/<레파지토리명>"
    }
  ]
}

4-3. ECR 프라이빗 리포지토리에서 이미지를 푸시하고 풀링하기 위한 최소 권한

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowPushPull",
      "Effect": "Allow",
      "Action": [
        "ecr:BatchGetImage",
        "ecr:BatchCheckLayerAvailability",
        "ecr:CompleteLayerUpload",
        "ecr:GetDownloadUrlForLayer",
        "ecr:InitiateLayerUpload",
        "ecr:PutImage",
        "ecr:UploadLayerPart"
      ],
      "Resource": "arn:aws:ecr:<리전>:<AWS 계정 ID>:repository/<레파지토리명>"
    }
  ]
}

📝 문제 2 : 빌드 후 ECR에 이미지가 배포되었을 때 자동으로 서비스에 배포하는 workflow 실행 시 ECR 배포 실패

{
   "Version":"2012-10-17",
   "Statement":[
      {
         "Sid":"RegisterTaskDefinition",
         "Effect":"Allow",
         "Action":[
            "ecs:RegisterTaskDefinition"
         ],
         "Resource":"*"
      },
      {
         "Sid":"PassRolesInTaskDefinition",
         "Effect":"Allow",
         "Action":[
            "iam:PassRole"
         ],
         "Resource":[
            "arn:aws:iam::<aws_account_id>:role/<task_definition_task_role_name>",
            "arn:aws:iam::<aws_account_id>:role/<task_definition_task_execution_role_name>"
         ]
      },
      {
         "Sid":"DeployService",
         "Effect":"Allow",
         "Action":[
            "ecs:UpdateService",
            "ecs:DescribeServices"
         ],
         "Resource":[
            "arn:aws:ecs:<region>:<aws_account_id>:service/<cluster_name>/<service_name>"
         ]
      }
   ]
}

📝 문제 3 : 컨테이너로 WAS와 DB를 따로 기동 후 WAS에서 DB 접근 불가

  • 원인
    1. compose로 함께 기동하지 않고 따로 기동할 경우 브릿지가 생성되지 않았다.
    2. container는 독립된 네트워크로 동작하므로 두 서버가 동일한 서브넷에 속해있지 않았기 때문에 애초에 통신이 불가능했다.
$ docker run --name servertest -p 3000:3000 --env MONGO_PASSWORD=secret --env MONGO_HOSTNAME=172.22.0.2 --env MONGO_USERNAME=root --network devops-04-s2-team9_default ohrory218/helloworld:1.1

> helloworld-was@1.0.0 start
> fastify start -l info app.js --address 0.0.0.0

[INFO] app.js file processing
[INFO] app.js file done
[DB CONNECTION STRING] mongodb://root:secret@172.22.0.2:27017/?authMechanism=DEFAULT
MongoServerSelectionError: connect ECONNREFUSED 172.22.0.2:27017
    at Timeout._onTimeout (/app/node_modules/@fastify/mongodb/node_modules/mongodb/lib/sdam/topology.js:277:38)
    at listOnTimeout (node:internal/timers:569:17)
    at process.processTimers (node:internal/timers:512:7) {
  reason: TopologyDescription {
    type: 'Unknown',
    servers: Map(1) { '172.22.0.2:27017' => [ServerDescription] },
    stale: false,
    compatible: true,
    heartbeatFrequencyMS: 10000,
    localThresholdMS: 15,
    setName: null,
    maxElectionId: null,
    maxSetVersion: null,
    commonWireVersion: 0,
    logicalSessionTimeoutMinutes: null
  },
  code: undefined,
  [Symbol(errorLabels)]: Set(0) {}
}
  • 트러블 슈팅
    • 기동된 mongodb 컨테이너 네트워크를 확인해서 환경 변수의 HOSTNAME에 IP주소를, network에 networks명을 집어넣는다.
$ docker container inspect mongodb
[
    {
#... 중략 ...#
            "Networks": {
                "devops-04-s2-team9_default": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": [
                        "mongodb",
                        "mongo",
                        "223966663fe2"
                    ],

# ... 중략 ....#
                    "IPAddress": "172.22.0.2",
# ... 하략 ....#

                }
            }
        }
    }
]
  • 해결 방법
    1. mongo DB의 config를 변경해 외부 네트워크에서 접근 가능하도록 설정
    2. (채택) WAS 컨테이너를 실행할 때 DB 컨테이너가 실행된 네트워크에서 실행되도록 수정
$ docker run --name servertest -p 3000:3000 --env MONGO_PASSWORD=secret --env MONGO_HOSTNAME=172.22.0.2 --env MONGO_USERNAME=root --network devops-04-s2-team9_default ohrory218/helloworld:1.1

> helloworld-was@1.0.0 start
> fastify start -l info app.js --address 0.0.0.0

[INFO] app.js file processing
[INFO] app.js file done
[DB CONNECTION STRING] mongodb://root:secret@172.22.0.2:27017/?authMechanism=DEFAULT
{"level":30,"time":1682772429521,"pid":18,"hostname":"87140ace8e46","msg":"Server listening at http://0.0.0.0:3000"}

📝 문제 4 : 웹 서버 자동화 파이프라인 구성 시 Source에서 권한 부족 문제 발생

https://blog.kakaocdn.net/dn/pxjSc/btsdeINuaLN/dCNjYYUG9AyKKCfagbAqXk/img.png

The provided role cannot be assumed: 'Access denied when attempting to assume the role 'arn:aws:iam::123456789012:role/service-role/AWSCodePipelineServiceRole-ap-northeast-2-helloworld-web''

https://blog.kakaocdn.net/dn/2Sodk/btsdemw3u1c/739Qz5wBN8TLSdk1nFy1y1/img.png

 

* 1. 검색 창에 EC2를 검색하여 들어간 후 EC2를 생성(EC2에 서버구동을 위한패키지 설치, 포트설정은 되어있어야한다.)*

> 작업 -> 인스턴스 설정 -> 태그 관리 클릭

태그를 생성하는 이유:

* > AWS에서 사용하는 리소스들을 태그별로 분류해 관리하기 위함(비용적, 유지보수적 측면에서 유리)*

2. 새로운 태그 추가

3. **_IAM 역할 수정_**

> 작업 -> 보안 -> IAM 역할 수정 클릭

IAM 역할 수정하는 이유:

* > 인스턴스가 다른 AWS 서비스와 통신할 때, 접근 권한을 주기 위함*

4. 새 IAM 역할 생성 클릭

5. 역할 만들기 클릭

6. AWS Deploy를 이용하여 EC2에 작업할 것이므로 AWS 서비스 선택

> EC2 선택

7. CodeDeploy가 S3에 있는 코드를 사용할 것이므로 'AmazonS3 FullAcess' 선택, **_EC2에 Agent를 설치하기 위해서 'AmazonSSMFullAccess' 도 선택해준다. 그리고 마지막으로 'AWSCodeDeployRole' 까지 선택해준다._**

AWSCodeDeployRole 사용 이유

> CodeDeploy에서 배포하는 애플리케이션을 관리하는 데 필요한 권한을 제공

> CodeDeploy 배포 그룹을 만들 때 사용됩니다. 배포 그룹은 배포할 대상을 지정하는 데 사용

8. 역할 이름 지정하고 '역할 생성' 클릭

https://blog.kakaocdn.net/dn/drEvVX/btsb60a8Fad/x4e4vYXvl9Guw7mJWmLtq0/img.png

9. ec2 뿐만아니라 aws 서비스인 codedeploy도 리소스에 접근할 수 있도록 추가

https://blog.kakaocdn.net/dn/bdldXH/btscvZ9HbL9/yUPWvG9QZqWudzBdweJE9K/img.png

10. 실습에 활용할 로컬 파일에 appspec.yml파일을 추가한다. 그리고 각 hook에 해당하는 파일들도 작성해준다.

> 소스코드의 위치를 지정하고, 각 hook에 해당하는 파일의 위치와 실행할 계정을 지정해준다.

> 배포 그룹 생성

11. AWS CodeDeploy 대시보드로 이동해 애플리케이션으로 이동하고 **_[ 애플리케이션 생성 ] 버튼을 클릭_**

12. 애플리케이션의 이름을 임의로 입력하고, 컴퓨팅 플랫폼을 'EC2/온프레미스'로 선택한 뒤, [ 애플리케이션 생성 ] 버튼을 클릭

13. 애플리케이션이 생성되면, 생성한 애플리케이션의 배포 그룹 탭을 클릭하여 [ 배포 그룹 생성 ] 버튼을 클릭

14. 배포 그룹의 이름을 임의로 입력하고, 서비스 역할 영역을 클릭한 후 전에 생성했던 'EC2Role'을 선택

15. 환경 구성 중 'Amazon EC2 인스턴스'를 선택하고, 태그 그룹에 EC2 인스턴스에 설정해놓았던 태그 키와 값을 선택

16. 로드 밸런싱 활성화 체크 해제 후, [ 배포 그룹 생성 ] 버튼을 클릭

> 배포 파이프라인 생성

17. CodePipeline 대시보드로 이동 후, [ 파이프라인 생성 ] 버튼을 클릭한다. 그리고 파이프라인 이름을 임의로 입력 후, [ 다음 ] 버튼을 클릭

18. 앞서 클라이언트에서 했던 파이프라인 생성과정을 진행해주고, GitHub연결을 이미 했으므로 생성해둔 GitHub연결을 가져온다.

19. 우리가 이용하는 서버 코드(애플리케이션)는 코드의 컴파일과 빌드 과정이 필요 없고 테스트 코드도 없으므로, [ 빌드 스테이지 건너뛰기 ] 버튼을 눌러 빌드 단계를 생략

20. 배포 스테이지의 각 항목을 적절하게 생성해준다. 이번엔 CodeDeploy를 사용하므로 CodeDeploy를 선택해준다.

21. 잘못된 것이 있는지 확인 후 파이프라인 생성

결과

> 환경변수 설정.

  1. 환경 변수를 AWS System Manager Parameter Store에서 설정할 것이기 때문에 AWS CLI를 먼저 EC2 instance에 설치해야 한다. 밑에 명령어를 차례대로 입력

https://blog.kakaocdn.net/dn/quCpo/btscyYWQXbO/LxFcp3TAJ0kcyxMLlTZyx1/img.pnghttps://blog.kakaocdn.net/dn/c2dKMz/btscoE6zhFc/YtDNkCmub6fGwo9dwVDCl1/img.pnghttps://blog.kakaocdn.net/dn/btAm31/btscfDfZnIM/lxkfC4njXXCVxdtAVFXAmK/img.png

AWS CLI를 다운로드 하기 위한 명령어

https://blog.kakaocdn.net/dn/Bs8T6/btscoE6zlvX/1KWbQNLk1cmSbSjZDClU81/img.png

AWS 버전을 확인

이렇게 되면 aws CLI는 설치가 완료된 것이다

  1. RDS 생성

검색 창에 RDS 입력 후 데이터베이스 생성을 클릭

https://blog.kakaocdn.net/dn/2IeE7/btscyGWle6G/lFwk4BBcuzzJDYMKLyr5e0/img.png

엔진 옵션에 원하는 데이터베이스 소프트웨어를 선택

https://blog.kakaocdn.net/dn/ci7Yym/btscwnpAuul/a5hKRQQ2DX7ro0lckYQEHK/img.png

탬플릿은 사용 용도에 따라 선택하면 되는데, 비용이 제일 싼 걸 선택했다

https://blog.kakaocdn.net/dn/bc8IQr/btscwESdMO1/D3ml77BrEqrj4XKUGkFSi1/img.png

스크롤을 내려 DB 인스턴스 식별자, 마스터 사용자 이름, 암호를 기입

https://blog.kakaocdn.net/dn/bEKDhE/btscjiJvjwG/ovCRg8S6jYFLPb6E5neMek/img.png

컴퓨터 리소스는 기존 EC2에 연결하고 싶으면 오른쪽을 아니면 왼쪽을 클릭

VPC는 기존 EC2 VPC에 연결할 수 있음

DB 서브넷 그룹도 기존 존재하던 것에 연결할 수 있음

https://blog.kakaocdn.net/dn/bFYleb/btscmLkpQBe/ac0qysqftUOsomh6kh1VB0/img.png

보안 그룹도 기존에 존재하던 거와 연결할 수 있으며, 데이터베이스 인증 방식은 암호 인증 선택

https://blog.kakaocdn.net/dn/bgwzrF/btscCA82W3p/1dLE32ttVbtV6eDFS7wkTK/img.png

데이터베이스 생성 클릭

https://blog.kakaocdn.net/dn/Qegg7/btscvZh5T1j/BiqGkxk0vfXTMCex6oZkB1/img.png

  1. Parameter Store에 환경 설정

검색 창에 Parameter Store 입력 후 오른쪽 상단에 파라미터 생성 클릭

 

이름 -> appspec.yml가 실행할 스크립트에 있는 환경 변수 이름 기입

값 -> 이전에 생성한 RDS 값 입력

> 이후 파라미터 생성 클릭

예)

이름 : DETABASE_HOST

생성 완료 시 밑에 사진처럼 생성

https://blog.kakaocdn.net/dn/dcSWRO/btscxmjrzd2/k6yoabsodR5vfXQs0hRIcK/img.png

  1. 깃허브에 appspec.yml이 인식할 수정한 스크립트 파일 push

https://blog.kakaocdn.net/dn/d7Yuh8/btscwFDDgHe/Tmgb6oJReMhBqtPxIGeaUK/img.png

> 환경 변수 이름은 Parameter Store에 추가한 이름,

> 값은 AWS SSM 파라미터 스토어에서 값을 가져와 환경 변수를 내보내는 셸 명령어

  1. 결과

AWS Parameter Store 장점

1. 보안: Parameter Store는 데이터를 안전하게 저장하기 위한 다양한 기능을 제공합니다. 예를 들어, AWS KMS를 사용하여 데이터를 암호화하고, IAM을 사용하여 액세스 권한을 관리할 수 있습니다.

2. 중앙 집중화된 관리: Parameter Store를 사용하면 설정 데이터를 중앙 집중화하여 관리할 수 있습니다. 이를 통해 여러 시스템에서 사용되는 설정 데이터를 효율적으로 관리할 수 있습니다.

3. 확장성: Parameter Store는 대규모 인프라스트럭처에서도 높은 확장성을 제공합니다. 필요에 따라 설정 데이터를 추가하거나 업데이트할 수 있습니다.

4. 유연성: Parameter Store는 다양한 데이터 형식을 지원합니다. 예를 들어, 문자열, JSON, 암호화된 문자열 등을 저장할 수 있습니다.

5. AWS 서비스와의 통합: Parameter Store는 AWS에서 제공하는 다양한 서비스와 통합됩니다. 예를 들어, AWS Lambda 함수에서 Parameter Store의 데이터를 사용할 수 있습니다.

클라이언트 배포 파이프라인

https://blog.kakaocdn.net/dn/cvVk1D/btsbVcpl2P9/xzI4DlwVtNbg8vHHxd83AK/img.jpg

요구사항

  • AWS 개발자 도구 서비스를 이용해서 배포 자동화 파이프라인을 구축.
    • CodePipeline을 이용해서 각 단계를 연결하는 파이프라인을 구축.
    • Source 단계에서 소스 코드가 저장된 GitHub 리포지토리를 연결.
    • Build 단계에서 CodeBuild 서비스를 이용하여 수동으로 진행했던 build 과정을 자동으로 진행.
    • Deploy 단계에서 결과물을 S3 버킷에 자동으로 전달.
  • 나중에 변경 사항을 GitHub 리포지토리에 반영했을 경우, 배포 과정이 자동으로 진행되어야 함.
  • 배포 과정에서 오류가 생길 경우, log 파일을 참조하여 문제점을 확인할 수 있어야 함 .

클라이언트 배포 자동화 파이프라인

1. buildspec.yml 작성

2. 파이프라인 구성

1. 검색 창에 CodePipeline 클릭 후 파이프라인 생성 클릭

2. 이름과 서비스 역할에서 새 서비스 역할 클릭 후 다음 클릭

3.

> 소스 공급자를 클릭해서 GitHub를 선택(build 할 데이터가 있는 곳을 선택하면 됨)

> 만약 GitHub를 선택했으면, GitHub 연결을 클릭 > 내 GitHub의 리파지토리와 연결을 위해서

https://blog.kakaocdn.net/dn/dfeSH5/btscvYvQR7p/8AOWNK5nzoksCGQc979LLk/img.png

4. 이름은 아무거나 입력

https://blog.kakaocdn.net/dn/Z6Nqy/btsb2TiDUYJ/5s9vwkS2xWaJRO3Z10Jfc0/img.png

5. github에 연결 클릭 하면 GitHub 앱의 새 앱 설치 클릭

https://blog.kakaocdn.net/dn/bKJH0p/btsctLQ8esi/fZpVPFOoMAX1RND4S35YsK/img.png

6. aws와 연결할 github 아이디 선택하고 연결

7.

> 실행할 소스 코드가 있는 자신의 리파지토리 선택

> install 버튼 클릭

8. 생성한 GitHub 앱 선택 후 연결 클릭

https://blog.kakaocdn.net/dn/bp0nyM/btsb60ofzCh/QRNEa78MMrkWPANmE3H0y1/img.png

9. 리파지토리 이름과 , 해당 리파지토리의 main 브랜치를 선택한 뒤 다음 버튼 클릭

10.

> 빌드 단계에서 codeBuild 프로젝트 생성

> 소스 단계를 통해 전달받은 코드를 테스트하거나 빌드하여 배포 단계로 전달하는 역할

11. 빌드 프로젝트 이름은 마음대로 지음

https://blog.kakaocdn.net/dn/bns4BO/btscvx6lPre/IJK4vr7oazvHYAKO5rrlkk/img.png

12.

> Ubuntu 운영체제를 선택

> 다른 운영체제를 선택할 시 yml 파일이 오류를 일으킬 수 있음

https://blog.kakaocdn.net/dn/cowbv3/btsbV7BLTU5/hKtszGMp0H8yVnrbTvZPnK/img.png

13.

> 로그 파일을 저장하는 서비스로 CloudWatch 혹은 S3를 이용

> S3에는 정적 웹 호스팅을 위한 버킷이 이미 생성

> 버킷의 수가 많아지면 과금이 될 가능성이 다소 커질 수 있기에, CloudWatch 서비스를 빌드 출력 로그 저장을 위한 서비스로 선택하여 리소스를 분산

https://blog.kakaocdn.net/dn/xE4Lj/btscwmQUPdr/BaZ11NZvLnIU1D1Iyduf5k/img.png

14. 다음 클릭

15. 최종적으로 만들어진 결과물을 전달 및 반영할 위치를 선택, client이므로 s3를 선택

https://blog.kakaocdn.net/dn/cLZjla/btsb2TXeI1O/7GBdMtMlIJUXnMS34RSHP0/img.png

16. s3를 만들었을 때 사용한 버킷 이름을 선택

17. 검토한 후 다 맞다면 파이프라인 생성 클릭

결과

> 배포가 성공하는 것을 확인할 수 있다

환경변수 설정

  1. AWS 검색창에 CodeBuild를 검색하여 클릭

https://blog.kakaocdn.net/dn/YnJ2h/btscFEwBMEd/zcbK0HxKw3NwM0fJt6KqBk/img.png

  1. 원하는 빌드 프로젝트 선택 후 오른쪽 상단에 편집 -> 환경 클릭

https://blog.kakaocdn.net/dn/b5eIMX/btscyYo2c6z/qtKgoW52Atb6raBuxspFOK/img.png

  1. 밑으로 스크롤을 쭉 내려 추가 구성을 클릭

https://blog.kakaocdn.net/dn/bGWNbh/btsczK5egx9/BJ19U4DeAI10uuw6f1Frk0/img.png

  1. 추가 구성을 클릭하면 새로 선택지가 나옴, 스크롤을 내려 환경 변수에 환경 변수 이름과 서버의 주소를 넣음 -> 환경 업데이트 클릭

https://blog.kakaocdn.net/dn/d8pleY/btscBDELWdL/3nY0GggzwCbxTI8Oe4AyM1/img.png

  1. 파이프라인에 변경사항 릴리스를 클릭

https://blog.kakaocdn.net/dn/bIlzST/btscAq6Eqn0/95XKFBLiVCThYqrO4GzEgK/img.png

결과

> 이렇게 나온다면 클라이언트와 서버의 연결이 성공한 것이다 . 물론, 서버가 이미 열려있는 상태여야 가능하다.<

배포 자동화

배포 자동화란 한 번의 클릭 혹은 명령어 입력을 통해 전체 배포 과정을 자동으로 진행하는 것을 말합니다.

 

배포자동화 장점

  • 먼저 수동적이고 반복적인 배포 과정을 자동화함으로써 시간이 절약됩니다.
  • 휴먼 에러(Human Error)를 방지할 수 있습니다. 여기서 휴먼 에러란 사람이 수동적으로 배포 과정을 진행하는 중에 생기는 실수를 뜻합니다. 배포 자동화를 통해 전체 배포 과정을 매번 일관되게 진행하는 구조를 설계하여 휴먼 에러 발생 가능성을 낮출 수 있습니다.

 

배포 자동화 파이프라인

파이프라인 구성 단계와 수행 작업

배포에서 파이프라인(Pipeline)이란 용어는 소스 코드의 관리부터 실제 서비스로의 배포 과정을 연결하는 구조를 뜻합니다.

파이프라인은 전체 배포 과정을 여러 단계(Stages)로 분리하는데, 각 단계는 파이프라인 안에서 순차적으로 실행되며, 단계마다 주어진 작업(Actions)을 수행합니다.

파이프라인을 여러 단계로 분리할 때, 대표적으로 쓰이는 세 가지 단계가 존재합니다. 

  1. Source 단계: Source 단계에서는 원격 저장소에 관리되고 있는 소스 코드에 변경 사항이 일어날 경우, 이를 감지하고 다음 단계로 전달하는 작업을 수행합니다.
  2. Build 단계: Build 단계에서는 Source 단계에서 전달받은 코드를 컴파일, 빌드, 테스트하여 가공합니다. 또한 Build 단계를 거쳐 생성된 결과물을 다음 단계로 전달하는 작업을 수행합니다.
  3. Deploy 단계: Deploy 단계에서는 Build 단계로부터 전달받은 결과물을 실제 서비스에 반영하는 작업을 수행합니다.

여기서 주의해야 할 점은 파이프라인의 단계는 상황과 필요에 따라 더 세분화되거나 간소화될 수 있다는 겁니다.

 

AWS 개발자 도구

AWS에는 개발자 도구 섹션이 존재합니다. 개발자 도구 섹션에서 제공하는 서비스를 활용하여 배포 자동화 파이프라인을 구축할 수 있습니다.

AWS 개발자 도구 서비스 목록

 

본격적인 배포 자동화 실습을 진행하기 전에, 배포 자동화 과에서 사용하는 AWS 서비스들에 대해서 간략하게 소개하는 시간을 가져보도록 하겠습니다.

 

CodeCommit

Source 단계를 구성할 때 CodeCommit 서비스를 이용합니다. CodeCommit은 GitHub과 유사한 서비스를 제공하는 버전 관리 도구입니다. CodeCommit과 GitHub의 차이점은 무엇일까요? 어떤 서비스가 우월하다기보다, 각 서비스가 가지는 장단점이 다릅니다.

GitHub과 비교했을 때 CodeCommit 서비스는 보안과 관련된 기능에 강점을 가집니다. 소스 코드의 유출이 크게 작용하는 기업에서는 매우 중요한 요소입니다. 다만 CodeCommit을 사용할 때는 과금 가능성을 고려해야 합니다. 프리티어 한계 이상으로 사용할 시 사용 요금이 부과될 수도 있습니다. 그런 이유로 사이드 프로젝트나 가볍게 작성한 소스 코드를 저장해야 할 경우에는 GitHub을 이용하는 것이 효과적이라고 볼 수 있습니다.

CodeBuild

Build 단계에서는 CodeBuild 서비스를 이용합니다. CodeBuild 서비스를 통해 유닛 테스트, 컴파일, 빌드와 같은 빌드 단계에서 필수적으로 실행되어야 할 작업을 명령어를 통해 실행할 수 있습니다.

CodeDeploy

Deploy 단계를 구성할 때는 기본적으로 다양한 서비스를 이용할 수 있습니다. 이번 실습에서는 CodeDeploy와 S3 서비스를 이용할 예정입니다. CodeDeploy 서비스를 이용하면 실행되고 있는 서버 애플리케이션에 실시간으로 변경 사항을 전달할 수 있습니다. 또한 S3 서비스를 통해 S3 버킷을 통해 업로드된 정적 웹 사이트에 변경 사항을 실시간으로 전달하고 반영할 수 있습니다.

CodePipeline

각 단계를 연결하는 파이프라인을 구축할 때 CodePipeline 서비스를 이용합니다.

Bare minimum requirements

1. 유닛 테스트를 통과시키세요.

먼저 테스트 주도 개발을 연습합니다. 직접 test/app.test.js를 수정하여 통과하지 않는 테스트를 모두 통과시키세요.

  1. 애플리케이션은 node.js로 작성되어 있습니다. node.js LTS 버전을 준비합니다.
  2. 먼저 애플리케이션의 의존성(dependency)을 설치해야 합니다. npm install 명령을 이용해 의존성을 설치합니다.
  3. 테스트가 통과하는지 확인하려면 npm test 명령을 이용합니다. 다음과 같이 테스트가 통과하지 않는 것을 먼저 확인하세요.
➜  npm test 

> mini-node-server@1.0.0 test
> mocha ./test

Server listening on http://localhost:4000


  유닛 테스트 101
    1) 결과에 대한 기대값(expectation value)를 비교하여 유닛 테스트를 할 수 있습니다
    2) 서버에 GET / 요청을 보내면 Hello World!라는 텍스트가 응답으로 옵니다
    3) 서버에 POST /upper 요청에 body를 실어 보내면 응답은 대문자로 돌려줍니다
    4) 서버에 POST /lower 요청에 body를 실어 보내면 응답은 소문자로 돌려줍니다


  0 passing (35ms)
  4 failing

  1) 유닛 테스트 101
       결과에 대한 기대값(expectation value)를 비교하여 유닛 테스트를 할 수 있습니다:
     AssertionError: expected 2 to equal '기댓값이 채워지지 않았습니다'
      at Context.<anonymous> (test/app.test.js:13:25)
      at processImmediate (node:internal/timers:463:21)

 (생략)

test/app.test.js 파일을 열어 통과하지 않는 테스트를 수정하세요. FILL_ME_IN이라고 적힌 곳에 기댓값을 적어주면 됩니다.

2. GitHub Action을 이용해서 Node.js CI를 적용하세요.

  1. node 버전은 16 버전으로 반드시 지정해야 합니다.
  2. 다음 상황에서 GitHub Action이 트리거되어야 합니다.
    • master로 push 했을 경우
    • pull request를 보낸 경우
  3. Pull Request로 제출하세요.

Getting Started

  • node.js 프로그램의 테스트를 위해서는 npm test 명령어를 CI, 즉 GitHub Action 상에서 자동으로 실행해줘야 합니다.
  • 먼저 공식 문서를 통해 GitHub Action의 사용방법을 알아봅시다.

Creating a starter workflow

 

Creating starter workflows for your organization - GitHub Docs

Overview Starter workflows allow everyone in your organization who has permission to create workflows to do so more quickly and easily. When you create a new workflow, you can choose a starter workflow and some or all of the work of writing the workflow wi

docs.github.com

Using starter workflow

 

Using starter workflows - GitHub Docs

About starter workflows GitHub offers starter workflows for a variety of languages and tooling. When you set up workflows in your repository, GitHub analyzes the code in your repository and recommends workflows based on the language and framework in your r

docs.github.com

 


실습과정

1. 유닛 테스트를 통과시키세요.

먼저 받아온 실습파일의 의존성을 설치하고 테스트 코드를 확인하고 테스트를 실행해 어떤 테스트가 통과돼야하는지 확인한다.

app.test.js파일 내용과 npm test 실행 결과 ▼

더보기

 

const { expect } = require('chai')
const request = require('supertest')
const { app, server } = require('../app')
const FILL_ME_IN = '기댓값이 채워지지 않았습니다'

describe('유닛 테스트 101', () => {

  after(() => {
    server.close()
  })

  it('결과에 대한 기대값(expectation value)를 비교하여 유닛 테스트를 할 수 있습니다', () => {
    expect(1 + 1).to.be.equal()
    expect(100 + 200).to.be.equal()
  })

  it('서버에 GET / 요청을 보내면 Hello World!라는 텍스트가 응답으로 옵니다', () => {
    return request(app)
      .get('/')
      .then(res => {
        expect(res.text).to.be.equal()
      })
  })

  it('서버에 POST /upper 요청에 body를 실어 보내면 응답은 대문자로 돌려줍니다', () => {
    return request(app)
      .post('/upper')
      .send('"coDeStaTes"')
      .set('Content-Type', 'application/json')
      .then(res => {
        expect(res.body).to.be.equal()
      })
  })

  it('서버에 POST /lower 요청에 body를 실어 보내면 응답은 소문자로 돌려줍니다', () => {
    return request(app)
      .post('/lower')
      .send('"coDeStaTes"')
      .set('Content-Type', 'application/json')
      .then(res => {
        expect(res.body).to.be.equal()
      })
  })
})

이제 테스트 결과가 어떻게 나와야하는지 확인했으니 테스트 코드를 적절하게 바꿔준다.

변경한 app.test.js파일 내용과 npm test 실행 결과 ▼

더보기
const { expect } = require('chai')
const request = require('supertest')
const { app, server } = require('../app')
const FILL_ME_IN = '기댓값이 채워지지 않았습니다'

describe('유닛 테스트 101', () => {

  after(() => {
    server.close()
  })

  it('결과에 대한 기대값(expectation value)를 비교하여 유닛 테스트를 할 수 있습니다', () => {
    expect(1 + 1).to.be.equal(2)
    expect(100 + 200).to.be.equal(300)
  })

  it('서버에 GET / 요청을 보내면 Hello World!라는 텍스트가 응답으로 옵니다', () => {
    return request(app)
      .get('/')
      .then(res => {
        expect(res.text).to.be.equal("Hello World!")
      })
  })

  it('서버에 POST /upper 요청에 body를 실어 보내면 응답은 대문자로 돌려줍니다', () => {
    return request(app)
      .post('/upper')
      .send('"coDeStaTes"')
      .set('Content-Type', 'application/json')
      .then(res => {
        expect(res.body).to.be.equal("CODESTATES")
      })
  })

  it('서버에 POST /lower 요청에 body를 실어 보내면 응답은 소문자로 돌려줍니다', () => {
    return request(app)
      .post('/lower')
      .send('"coDeStaTes"')
      .set('Content-Type', 'application/json')
      .then(res => {
        expect(res.body).to.be.equal("codestates")
      })
  })
})

잘 실행되는 것을 확인할 수 있다.


2. GitHub Action을 이용해서 Node.js CI를 적용하세요.

이 영상을 참고해 실습을 진행했다.

테스트를 진행할 레포지토리에 .github/workflows/nodejs.yaml 파일을 생성한다.

해당 파일의 구조는 다음과 같다.

name: Node.js CI #워크플로우의 이름을 적어준다.

on: # 워크플로우가 실행되는 EVENT에 대해 작성한다. master 브랜치에 push 또는 pull request가 올 경우 실행된다.
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs: # 실행되는 워크플로우의 작업을 지정한다. 여러 Job이 있을 경우, Default로 병렬 실행을 한다.
  build: # build하는 job이 존재하고 그 아래에 실행되는 step이 존재하는 구조다.

    runs-on: ubuntu-latest # 어떤 OS에서 실행될지 지정한다.

    strategy: # 어떤 언어의 어떤 버전에서 테스트할지 지정할 수 있다.
      matrix:
        node-version: [16.x]

    steps: # 어떤 액션들을 사용할지 지정한다. uses는 이미 만들어진 액션을 사용할 때 지정한다.
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm install
      - run: npm run build --if-present
      - run: npm test

 

이렇게 파일을 작성하고 GitHub Actions 탭에 들어가 작동된건지 확인해보면

잘 작동하는 것을 확인할 수 있다!!

CI/CD 파이프라인

CI/CD 파이프라인이 무엇인지는 아래 게시글에서 확인할 수 있다.

https://dratini.tistory.com/entry/DevOps-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8

[[DevOps] CI/CD 파이프라인

CI/CD 파이프라인은 소프트웨어 개발 프로세스에서 개발, 테스트 및 배포를 자동화하기 위한 과정을 말한다. 이 파이프라인은 개발자들이 소프트웨어 개발에 대한 변경 사항을 더욱 쉽고 빠르게

dratini.tistory.com](https://dratini.tistory.com/entry/DevOps-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8)

이번엔 실제로 사용되는 CI/CD를 위한 툴들을 소개한다.

CI Tools


Jenkins

Wikipedia에 따르면 Jenkins는 다음과 같이 설명할 수 있습니다.

Jenkins는 오픈소스 자동화 서버입니다. 빌드, 테스트, 배포와 같은 소프트웨어 개발의 일부분을 자동화하는 데 도움을 주며, 지속적 통합과 지속적 배포를 돕습니다. 서버 기반의 시스템이며... (중략) ... Git과 같은 버전 관리 시스템을 지원합니다. 또한 쉘 스크립트를 실행할 수 있습니다.

https://www.jenkins.io/

 

Jenkins

Meet with Jenkins Community at cdCon + GitOpsCon 2023 The Continuous Delivery Foundation (CDF) is happy to host its fourth flagship event, cdCon, taking place on May 8–9, 2023 in Vancouver, Canada as cdCon + GitOpsCon, co-organized with the Cloud Native

www.jenkins.io

www.jenkins.io](https://www.jenkins.io/)

특징

  • 설치형: 별도의 서버가 필요합니다.
  • 다양한 플러그인을 활용할 수 있습니다.
  • 쿠버네티스, Docker 등과 호환됩니다.
  • 다양한 운영체제에서 사용이 가능합니다.

Travis CI

Wikipedia에 따르면 Travis CI는 다음과 같이 설명할 수 있습니다.

Travis CI는 호스트형(hosted) 배포 자동화 서비스로, GitHub 및 Bitbucket 등에서 호스팅되는 소프트웨어 프로젝트를 빌드하고 테스트하는 데 사용됩니다.

https://www.travis-ci.com/

 

Home – Travis-CI

Simple YAML configs

www.travis-ci.com

www.travis-ci.com](https://www.travis-ci.com/)

특징

  • 클라우드 서비스(SasS) 형태로 사용할 수 있습니다.
  • Travis 자체에서 호스팅을 해주기 때문에 관리적인 측면에서 편리합니다.
  • Clojure, Erlang, Groovy Haskell, Java, JavaScirpt, Node.js, Perl PHP, Rython, Ruby 등의 다양한 언어를 지원합니다.

GitHub Action

특징

  • GitHub 저장소를 기반으로 소프트웨어 개발 Workflow를 자동화할 수 있는 툴입니다.
  • GitHub 마켓 플레이스를 통해 여러 사람이 공유한 Workflow를 찾을 수 있으며, 자신이 직접 만들어 공유할 수도 있습니다.
  • 공개 저장소를 무료로 사용할 수 있으며, 비공개 저장소 같은 경우 무료 사용량 이후에 요금이 부과됩니다.
    • 한 달에 500MB 스토리지와 실행 시간 2,000분(minute)까지 제공됩니다.
  • GitHub Actions는 GitHub 리포지토리와 완벽하게 통합되어 있어, 별도의 서드파티 도구를 사용하지 않고도 CI/CD 파이프라인을 구축할 수 있습니다. 이로 인해, 설정 및 관리가 간편해지며 개발 프로세스가 더욱 통합됩니다.
  • Github Actions 레퍼런스: Learn GitHub Actions - GitHub Docs

https://github.com/features/actions

 

Features • GitHub Actions

Easily build, package, release, update, and deploy your project in any language—on GitHub or any external system—without having to run code yourself.

github.com

github.com](https://github.com/features/actions)


지속적 통합

소프트웨어 개발 분야의 설계 및 개발 방법론에 있어서 저명한 마틴 파울러는 지속적 통합에 대해 다음과 같이 정의합니다.

팀 구성원이 각자의 작업을 자주 통합하는 소프트웨어 개발 방식 - Martin Fowler

이 방법을 도입함으로써 얻을 수 있는 장점에 대해서는 앞서 다음과 같이 설명했습니다.

  • 버그를 일찍 발견할 수 있다. → 문제 조기 식별 가능
  • 빌드 및 테스트와 같이 사람이 해야 할 일들을 자동화할 수 있다. → 업무량 감소
  • 테스트가 완료된 코드에 대해 빠른 전달이 가능하다. → 업무 효율 증가
  • 지속적인 배포가 가능해진다. → 업무 안정성 증가
  • 개발자의 생산성을 향상할 수 있다. → 업무 효율 증가

지속적 통합 원칙

  1. 단일 소스 레파지토리를 유지해야 합니다.
    • 프로젝트에서 제품을 빌드하기 위해 함께 조정해야 하는 수많은 파일이 포함되기 때문에 추적하기 편하게 만들 기 위해서라도 단일 소스 레파지토리 구조를 유지해야합니다.
  2. 빌드를 자동화해야 합니다.
    • 사람들에게 이상한 명령을 입력하게 하거나 대화 상자를 클릭하게 하는 것은 시간 낭비입니다.
    • 빌드가 수동으로 진행된다면, 수많은 실수를 낳을 수 있습니다.
  3. 셀프 테스팅 빌드를 만들어야 합니다.
    • 빌드 프로세스에 자동화된 테스트를 포함 함으로써 버그를 더 빠르고 효율적으로 파악할 수 있습니다.
    • 지속적 통합의 일부인 테스트 주도 개발(TDD)을 통해 손상된 빌드를 즉시 확인, 수정할 수 있습니다.
  4. 매일 메인라인에 커밋을 해야 합니다.
    • 각자의 진행 상황을 추적하는 데 도움이 됩니다.
    • 짧은 주기로 커밋을 하므로, 충돌이 발생한 후 빠르게 충돌 상황을 감지할 수 있습니다.
      • 해당 시점에는 충돌이 많이 발생하지 않아 문제를 쉽게 해결할 수 있습니다.
      • 만약 통합의 빈도가 길어 충돌을 늦게 발견하게 된다면 문제를 해결하기 매우 어려울 수 있습니다.
  5. 메인라인: 시스템의 현재 상태를 의미합니다. 회사마다 어떤 브랜치를 메인라인으로 두는지는 조금씩 다르지만, 여기서는 master 브랜치를 메인라인으로 취급하도록 하겠습니다.
  6. 모든 팀원이 무슨 일이 일어나고 있는지 알아야 합니다.
    • 지속적 통합은 커뮤니케이션에 관한 것이므로 모든 사람이 시스템 상태와 시스템에 적용된 변경 사항을 쉽고 명확하게 확인할 수 있어야 합니다.

이 외에도 많은 원칙들이 있다. 해당 내용들은 아래 링크에서 확인할 수 있다.

https://www.martinfowler.com/articles/continuousIntegration.html

[Continuous Integration

Every developer integrates their work into mainline at least every day.

martinfowler.com](https://www.martinfowler.com/articles/continuousIntegration.html)


지속적 통합에서 테스트가 중요한 이유

테스트를 통해 결함과 버그를 조기에 발견할 수 있으며, 이는 개발자의 생산성을 향상할 수 있습니다.

제품의 결함과 버그를 발견하고 수정하는 것은 소프트웨어의 품질을 보증하고, 더 안정적이고 사용하기 쉽게 만듭니다.


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

테스트 주도 개발이란 테스트가 기능의 디자인을 주도하는 반복적인 개발 방법론을 뜻합니다.

테스트 주도 개발의 필요성을 알기 위해선 TDD 이전의 개발 방식에 대해 학습할 필요가 있습니다.


기존 개발과정

기존의 개발 프로세스는 아래와 같습니다.

  1. 요구사항 분석
  2. 요구 사항을 토대로 디자인(설계)을 진행
  3. 설계에 맞추어 기능을 개발.
  4. 구현 완료 후 수동으로 기능을 테스트
  5. 원하는 대로 동작하지 않거나 문제가 발생하면 디버깅을 통해 원인을 파악하고 수정
  6. 3 - 4의 과정을 개발이 완료될 때까지 반복

기존 개발과정의 문제점

수동 테스트와 디버깅: 기능 개발 완료 후 수동으로 테스트하고 디버깅을 통해 원인을 파악하고 수정하는 과정이 시간이 많이 소요되었습니다. 그래서 개발 및 배포 속도가 느려지고, 프로젝트 전반에 걸친 효율성이 저하되었습니다.

통합 문제: 개별적으로 개발된 코드들을 일정 시점에 통합하는 과정에서 발생하는 문제점들이 있었습니다. 이러한 통합 과정에서 호환성 문제나 버그가 발생하기 쉬워, 추가적인 디버깅과 수정 작업이 필요했습니다.

협업의 어려움: 팀 간의 협업이 제한적이어서, 코드 통합 과정에서 문제가 발생할 확률이 높았습니다.

높은 유지보수 비용: 코드의 변경이나 수정이 빈번하게 발생할 때마다 수동으로 테스트를 수행하고 디버깅을 해야 하므로 유지보수 비용이 높습니다. 이는 개발 비용과 시간을 증가시키는 요인이 됩니다.


테스트 주도 개발 과정

기존의 개발 프로세스를 보완하기 위해 태어난 테스트 주도 개발(TDD)의 프로세스는 아래와 같습니다.

  1. 요구사항 분석
  2. 요구 사항을 토대로 디자인(설계)을 진행
  3. 설계를 기반으로 기능 테스트 진행
    1. 실패 시 다시 설계
  4. 테스트가 성공했다면 개발 진행
  5. 3 - 4의 과정을 개발이 완료될 때까지 반복

기존 개발환경에서 개선된 점

설계 → 개발 → 테스트로 이어지던 기존의 개발 프로세스를 설계 → 테스트 → 개발의 프로세스로 변경하여 버그를 조기에 발견하고 해결할 수 있게 되었습니다.

TDD의 설계 → 테스트 → 개발의 프로세스는 변경 점에 따라 테스트를 진행해야 하는 상황에 대한 부담을 줄여 주었습니다.


테스트 주도 개발 사이클

TDD의 테스트는 큰 단위의 문제를 작은 단위로 나누어, 지속적인 피드백을 통해 목표를 개선해 나가는 방향으로 진행됩니다.


테스트 주도 개발의 장점

  1. 더욱 명확한 기능과 구조를 설계할 수 있습니다.
  2. 재사용성이 고려된, 모듈화된 코드를 작성할 수 있습니다.
  3. 설계 수정 시간과 디버깅 시간의 단축
    1. 단위 테스트 기반의 테스트 코드를 작성하기 때문에 추후 프로그램에 문제가 발생할 경우, 각각의 모듈별로 테스트를 진행하면서 문제 지점을 쉽게 찾아낼 수 있습니다.
  4. 완성도 높은 설계
    1. 코드의 기능, 정의 등 구조적인 문제에 대하여 명확하게 접근할 수 있으며 다양한 예외상황에 대해서도 고려하게 되므로 이는 완성도 높은 설계로 이어집니다.
  5. 유지 보수의 용이성
    1. 프로젝트에 어떤 기능을 추가하는 등의 유지 보수를 해야 하는 상황이라면 항상 기존 코드들에 끼칠 영향에 대해 생각해야 합니다.
    2. TDD 이전의 개발 방식에선 단순한 기능이라도 수정되거나, 추가되는 경우에는 많은 코드에 대하여 테스트를 진행해야 했으나, TDD를 진행한다면 변경 점에 따른 테스트를 진행해야 하는 상황에 대한 부담이 줄어들 수 있습니다.

테스트의 종류

TDD에 활용할 수 있는 테스트의 종류에 대해 알아봅니다. 단위 테스트와, 통합 테스트 그리고 E2E 테스트에 대해 알아봅니다.


단위 테스트

말 그대로 작은 단위의 테스트입니다.
검증이 필요한 코드에 대해 테스트 케이스를 작성하는 과정을 말합니다.
유닛 테스트의 장점으로는 즉각적인 피드백이 나오는 것을 들 수 있습니다. 다만, 하나의 메서드가 잘 작동함은 보장할 수 있지만 그들이 결합하는 시점에서도 잘 작동하는지에 대해서는 보장할 수 없기 때문에 염두에 두어야 합니다.

통합 테스트

모듈을 통합하는 과정에서 모듈 간 호환성의 문제를 찾아내기 위해 수행되는 테스트입니다.

그래서 단위 테스트에서 찾지 못하는 통합 시 발생하는 버그 등을 찾을 수 있습니다.


단위 테스트 및 통합 테스트 시 사용하는 도구

  • mocha, chai (JavaScript)
  • JUnit (Java)

E2E 테스트 (End To End Test)

전체 시스템이 제대로 동작하는지 확인하기 위한 테스트입니다.
그래서 사용자의 입장에서 사용자가 사용하는 상황을 가정하고 시뮬레이션을 진행합니다.

장점

실제 상황에서 발생할 수 있는 에러를 미리 발견할 수 있다.

단점

테스트 작성 시 들어가는 비용이 너무 많습니다.
수행 속도가 느립니다.
"실패했다"라는 결과만 있기 때문에 피드백의 질이 낮습니다.

 

E2E 테스트에 대해 더 공부하고 싶다면 아래 링크를 통해 공부하는 것도 좋다.

https://youtu.be/wycIcpuLkzU

https://stackify.com/3-best-practices-for-end-to-end-testing/

 


통합 테스트와 E2E 테스트의 차이

항목 통합 테스트 (Integration Testing) 종단간 테스트 (End-to-End Testing)
방식 앱 구성 요소들이 함께 잘 작동하는지 확인한다. 사용자 경험을 통해 제품을 테스트함.
범위 여러 구성 요소를 포함할 수 있지만 대부분 전체 스택은 아님. 테스트 범위가 넓고 전체 애플리케이션 기술 스택을 포함함.
목적 구성 요소들이 함께 작동할 때 연결 문제 발견을 위함. 앱의 사용자 경험을 확인하기 위함.
구현 비용 비교적 저렴함. 하드웨어와 소프트웨어 측면에서 구현 비용이 더 많이 듦.
테스트 수준 단위 테스트보다 상위 수준. 통합 테스트보다 상위 수준.
수행 속도 빠름. 느림.

 

표에서 통합 테스트와 종단간 테스트 각각의 목적, 범위, 이유, 구현 비용, 테스트 수준 및 수행 속도에 대한 차이를 확인할 수 있습니다.

도커(Docker)?

 도커는 컨테이너 기반의 오픈소스 가상화 플랫폼이다. 여기서 컨테이너는 격리된 공간에서 프로세스를 실행하는 기술을 말하는데, 이 기술을 사용하면 하나의 컨테이너에 애플리케이션과 그것을 실행하는 데 필요한 모든 종속성을 포함한다.

 다시말해, 컨테이너환경에서 구축한 것은 어떤 환경에서도 똑같이 작동한다는 특징이 있다.

 

컨테이너 방식을 사용하면 얻을 수 있는 이득은 매우 많지만 몇가지만 말해보자면

  1. 의존성 충돌 문제의 해결
  2. 개발과 배포 환경의 일치
  3. 훨씬 쉬운 수평확장
  4. 각 서버에 새로운 내용을 배포하는 난이도 하락

일 것이다.

 이 장점들 모두 컨테이너안에 구축된 환경이 컨테이너 밖과 완전히 격리되는 특징으로부터 생긴다.

 그렇기 때문에 각기 다른 환경에서도 같은 컨테이너를 사용해 배포한다면, 똑같이 작동할 수 있다.

 

즉, 아래와 같은 끔찍한 상황을 미연에 방지할 수 있다.

도커의 주요 키워드

 

이미지 (Image)

컨테이너를 생성하기 위한 템플릿으로, 애플리케이션과 종속성을 포함한다. 이미지는 Dockerfile을 통해 만들어지며, 다른 사용자와 공유할 수 있다. 즉, 내 컨테이너 내부환경을 다른 사용자와 나눌 수 있다.

 

레지스트리 (Registry)

컨테이너를 생성하기 위한 템플릿으로, 애플리케이션과 종속성을 포함한다. 이미지는 Dockerfile을 통해 만들어지며, 다른 사용자와 공유할 수 있다. 즉, 내 컨테이너 내부환경을 다른 사용자와 나눌 수 있다.

 

Dockerfile 

도커 이미지를 생성하기 위한 스크립트 파일로, 애플리케이션과 실행 환경을 설정한다. Dockerfile에는 베이스 이미지, 필요한 소프트웨어 설치, 애플리케이션 코드 복사 등의 명령들이 들어간다.

 

Docker Hub

도커 이미지를 공유하는 공식 저장소로, 다양한 오픈소스 프로젝트 및 기업용 이미지를 찾을 수 있다. 개인이나 팀이 공유하고 싶은 이미지를 업로드하고 다운로드할 수 있는 플랫폼이다.

https://hub.docker.com/

 

Docker Hub Container Image Library | App Containerization

Deliver your business through Docker Hub Package and publish apps and plugins as containers in Docker Hub for easy download and deployment by millions of Docker users worldwide.

hub.docker.com

 

Docker Engine

도커 이미지를 실행하고 관리하는 소프트웨어다. 도커 엔진은 Dockerfile을 사용하여 이미지를 빌드하고, 이미지를 컨테이너로 실행한다.

 

Docker Compose

여러 컨테이너로 구성된 애플리케이션을 정의하고 관리하는 툴이다.

YAML 파일 형식으로 작성된 docker-compose.yml을 사용하여 서비스, 네트워크, 볼륨 등의 설정을 작성해두면, 한 번의 명령어로 편리하게 애플리케이션을 시작하거나 중지할 수 있다.

 

볼륨(Volume)

컨테이너의 데이터를 영구적으로 저장하는 방식이다.

컨테이너는 일시적이고 변경 가능하다. 여기서 볼륨을 사용하면 컨테이너가 삭제되거나 재시작될 때 데이터가 손실되지 않도록 데이터를 호스트 시스템에 저장할 수 있다.

물론 볼륨은 여러 컨테이너에서 공유하는 것도 가능하다.

 

도커 설치

우분투에서 도커 엔진을 설치하려면 아래 명령어를 입력해 도커 패키지를 설치해준다.

sudo apt update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

설치가 다 됐다면 한번 아래 명령어를 사용해서 제대로 설치 된건지 확인해본다.

sudo docker --version

sudo docker run hello-world

 

docker-compose 설치

우분투에서 도커 컴포즈를 설치하려면 아래 명령어를 입력해 설치해주면 된다.

 sudo apt-get update
 sudo apt-get install docker-compose-plugin

설치가 다 됐다면 한번 아래 명령어를 사용해서 제대로 설치 된건지 확인해본다.

 docker compose version

결과가 제대로 나오면 성공이다.

1차 팀 프로젝트 소개

섹션1. 팀프로젝트 주제는 쇼핑몰이었다.

프로젝트의 목표는 요구사항을 충족하는 DB와 API를 설계하고 설계에 맞는 ERD와 API명세를 작성하는 것으로 시작해, 명세된 기능을 fastify를 사용해 웹서버를 구현해 제작한 API가 잘 작동하는지 확인하는 것이다.


필수 요구사항

  • 사용자는 모든 상품을 조회할 수 있다
  • 사용자는 특정 분류의 상품을 조회할 수 있다(상품분류, 브랜드명, 가격, 상품명)
  • 사용자의 타입이 판매자인 경우 자신의 상품을 등록할 수 있다
  • 사용자는 상품을 장바구니에 담을 수 있다
  • 사용자는 자신의 장바구니를 조회할 수 있다
  • 사용자는 자신의 장바구니에 있는 상품의 수량을 변경시킬 수 있다
  • 사용자는 상품을 자신의 장바구니에서 제외할 수 있다

추가 요구사항

  • 회원가입, 로그인, 로그아웃을 할 수 있다.
  • 관리자는 모든회원의 정보를 조회할 수 있다.
  • 사용자는 자신의 정보를 조회할 수 있다.
  • 관리자는 회원을 삭제할 수 있다.

DB 설계

쇼핑몰 프로젝트 ER-Diagram

필요한 테이블은 크게 users, products, Cart테이블에 더해서 product 컬럼 내용의 중복이 많을 수 있는 category와 brand는 따로 테이블을 만들어서 관리한다.

Brands와 Categories는 이 설명만으로 충분하기 때문에 나머지 테이블을 살펴보면

  • users 테이블은 pk인 id, 그리고 유저가 어떤타입(일반, 관리자, 판매자)인지 나타내는 type과 password, email 컬럼으로 구성된다.
  • products 테이블은 pk인 productid, 그리고 판매자(user)의 id인 sellerId, 해당 브랜드의 id인 brandid, 해당되는 카테고리의 id인 categoryid를 각 해당테이블과의 외래키로, 나머지는 상품명인 productName과 가격인 price, 마지막으로 상품 설명인 desc로 이뤄진다.
  • Cart 테이블은 pk인 id, 그리고 장바구니 주인(user)의 id인 userId, 장바구니에 담긴 상품의 id인 productid를 각 해당테이블과의 외래키로, 나머지는 담긴 상품의 양인 qty로 이뤄진다.

API 명세

Method Path Request Header Request Body Response Status Code Response Body
GET /product Content-Type: application.json - 200 OK [ { "sellerID": 1, "brandID": 1, "categoryID": 1, productName": "테스트 상품.", "price: 50000", "desc": "테스트용 상품입니다." }, { "sellerId": 2, "brandId": 2, "categoryId: 2, "productName": "테스트 상품2", "price": 500000, "desc": "테스트용 상품2입니다." } ]
GET /product/:productId Content-Type: application.json - 200 OK 404 Not Found { { "sellerID": 1, "brandID": 1, "categoryID": 1, productName": "테스트 상품.", "price: 50000", "desc": "테스트용 상품입니다." } }
POST /product Content-Type: application.json, Authorization { "brandId": 1, "categoryId": 1, "productName": "테스트 상품.", "price": 50000, "desc": "테스트용 상품입니다." } 201 OK 400 Bad Request 403 Forbidden { "sellerID": 1, "brandID": 1, "categoryID": 1, productName": "테스트 상품.", "price: 50000", "desc": "테스트용 상품입니다." }
PUT /product Content-Type: application.json, Authorization { "brandId": 2, "categoryId": 2, "productName": "테스트 상품 수정.", "price": 50000, "desc": "테스트용 상품 수정입니다." } 201 OK, 400 Bad Request 403 Forbidden { "brandId": 2, "categoryId":2, "productName": "테스트 상품 수정.", "price":50000, "desc": "테스트용 상품 수정입니다." }
DELETE /product/:productId Content-Type: application.json, Authorization { "productID": 1 } 200 OK 204 No Content 400 Bad Request { "Success", "Bad Request" }
GET /cart Content-Type: application.json - 200 OK [ { "cartId": 1, "productId": 1, "productName": "상품명", "brandName": "브랜드명", "categoryName": "분류명", "price": 1000, "desc": "설명" } ]
POST /cart Content-Type: application.json, Authorization { "productId": 1 } 201 OK "Success"
PUT /cart/{cartId} Content-Type: application.json, Authorization { "productID": 1, "Qty": 1 } 200 OK 204 No Content 400 Bad Request "Success", "Bad Request"
DELETE /cart/{cartId} Content-Type: application.json, Authorization { "productID": 1 } 200 OK 204 No Content 400 Bad Request "Success", "Bad Request"
GET /user Content-Type: application.json Authorization - 200 OK [ { "userId": 2, "email": test@test.com, "password": 1234 }, { "userId": 3, "email": test2@test.com, "password": 1234 } ]
GET /user/:userId Content-Type: application.json Authorization - 200 OK 204 No Content { "userId: 2, "email": test@test.com "password": 1234 }
POST /signUp Content-Type: application.json, Authorization { "email": test@test.com "password": 1234 } 201 Created 400 Bad Request "Created", "Bad Request"
POST /signIn Content-Type: application.json { "email": test@test.com "password": 1234 } 200 OK 401 Unauthorized "OK", "Unauthorized"
PUT /user/:userId Content-Type: application.json, Authorization { "email": test@test.com "password": 1234 } 200 OK 400 Bad Request "Success", "Bad Request"
DELETE /user/:userId Content-Type: application.json, Authorization - 200 OK 400 Bad Request "Success", "Bad Request"

 API 명세서를 작성하면서 어떻게 API를 구성해야 구조가 직관적이어서 구현할 때 보기 편할지 고민을 많이 했다.

 그 결과 결국 상품, 사용자, 장바구니 별로 API 경로를 구분해 작성하는 것이 구현에 있어서도, 테스트에 있어서도 가장 직관적일 것이라고 판단해 API url을 정의했다. 그리고 request body값과 response값의 정의는 개발작업 중 편의를 위해서 개발 도중 변경하는 노선을 택했다.


구현작업 중 마주했던 이슈들 

초반 프로젝트 방향성 정리 부족으로 인한 의사소통 문제

 

 처음 프로젝트를 시작할때 서비스를 만듦에 있어서 확실히 정하고 넘어가야 하는 부분에 대한 충분한 의사소통이 없었던 점이 아쉽다.

 프로젝트를 진행하면서 이 프로젝트의 부분간의 연관점을 어떻게 다룰 것인가에 대해서 미리 정하지 않아서 서로가 서로의 작업 끝나는 것을 기다리면서 시간을 허비하거나 부분간의(ex. 깃/깃허브는 어떻게 사용할지) 유기적인 상호작용이 제대로 되지 않는 문제가 있었다. 

 

의사소통 문제로 인한 작업량 불균형 문제

 

앞서 언급했던 의사소통 문제의 연장선으로, 팀원들 간의 논의가 이뤄지지 않은 문제들이 속속 튀어나오면서 개발에 익숙한 팀원들이 프로젝트를 진행하게 되는 상황이 발생해버렸다.  그바람에 개발에 익숙하지 않았던 팀원들이 작업에 제대로 참여하지 못하게 됐고, 결과적으로 역량강화를 위해서 했어야하는 경험들을 팀원들 모두가 충분히 하지는 못했다고 생각한다.

 

어떻게 해야 해결할 수 있을까?

 앞으로는 프로젝트를 진행할 때 시간을 조금 할애하더라도 팀원들간의 차이를 고려해 프로젝트의 어떤작업을 어떻게 진행할지 확실히 해두고 다음 단계로 넘어가야겠다고 다짐했다. 

 그리고 프로젝트 진행 중에도 작업이 버거워보이는 팀원들에게 먼저 도와주러 가는 태도를 더 적극적으로 가져야겠다는 생각이 들었다.

 


 

깃허브 오류 : git pull 명령어 사용시 에러( Need to specify how to reconcile divergent branches. )

 

이 오류는 클론 받아온 로컬 브랜치와 원격 브랜치가 충돌이 있는 상태에서 merge(병합)하려고 할 때 발생한다. 그래서 이 문제를 해결하려면 먼저 충돌을 해결한 뒤에 병합을 진행해야 하는데, 해결법은 다음과 같다.

  1. 로컬 브랜치의 변경 사항을 커밋해준다.
  2. 로컬 브랜치 - 원격 브랜치의 동기화를 위해서 pull을 수행해준다.
  3. 충돌이 발생한 파일을 찾아서 수동으로 충돌을 해결해준다. 충돌은 <<<<<<< HEAD, =======, >>>>>>> 같은 마커로 표시된다.
  4. 충돌이 해결된 파일을 다시 커밋하고, 병합을 완료하면 해결된다.

Postgre 데이터베이스 오류 : 보낸쿼리에서 컬럼명을 찾지 못하는 문제 에러 (column "productname" does not exist)

 

`update public.products set productName = COALESCE($1, productName), price = COALESCE($2,parsedPrice), "desc" =COALESCE($3,"desc") where id=$4`, 
              [productName, parsedPrice, desc, productId]

 이 쿼리문이 Postgre 데이터베이스에서 컬럼명을 찾지 못하는 오류였다.

 이 오류는 알고보니 'productName'컬럼명처럼 대소문자를 구분해서 작성한 구문이 있어서 생기는 오류였다.

 Postgre는 기본적으로 컬럼 이름을 소문자로 해석하는데, 그래서 나는 분명 컬럼명을 대소문자로 구분해 적었지만 Postgre는 컬럼명을 모두 소문자로 해석해서 일어난 오류이다.

 

 

이 오류를 해결하는 법은 매우 간단하다. 아래처럼대소문자를 구분해 적어둔 구문에다가 쌍따옴표를 씌워주면 된다.

`update public.products set "productName" = COALESCE($1, "productName"), price = COALESCE($2,price), "desc" =COALESCE($3,"desc") where id=$4`, 
            [productName, parsedPrice, desc, productId]

이렇게 쿼리문을 작성해주면 오류가 깔끔하게 해결되는 것을 볼 수 있다.

 

 


데브옵스 부트캠프에서 첫 프로젝트 작업이었다. 프젝 진행중에 생기는 상황을 보면서 미리 논의를 하는게 얼마나 중요한지 조금이나마 께닫는 시간이 된 것 같다. 복잡하게 흘러가는 상황에 따라오지 못하는 팀원들을 보며 안타까웠고, 앞으로도 진행할 다른 프로젝트들에 있어서는 이번 프로젝트와 같은 문제를 반복하지 않도록 신경 많이 써야겠다. 

 그리고 회고를 쓰면서 아쉬웠던 점은 구현작업 도중에 마주쳤던 오류, 이슈들에 대한 기록들을 소홀하게 했다는 것이다. 많은 에러와 싸우며 완성한 프로젝트임에도 이 해결과정을 남기지 못하는 점이 너무나도 아쉽다. 앞으로는 오류 마주칠 때마다 입맛다시면서 기록해야겠다 ㅎㅎㅎ

+ Recent posts