Skip to content

3주차 발표

백승주 edited this page Mar 29, 2023 · 1 revision

프로젝트 변경점

  1. Spotless -> CheckStyle: 구글 포맷에서 tabSize, space를 변경하여도
    이를 spotless build check를 할 때 반영하는 어려움이 존재
    또한 Record를 인식하지 못하는 문제 발생, 또한 인텔리제이 기본 포맷팅과 상이한 점이 너무 많아서 CheckStyle로 변경하게되었습니다.

  1. Jacoco 적용: 현재 Code Coverage를 기반으로 추후 커버리지 방식과 코드 커버리지 limit을 정하고 진행하려함
    그러나 지금 컨트롤러 테스트 코드를 작성하지 않아,
    이번주 코드의 리팩토링과 더불어 컨테이너 테스트 코드 작성 후 활성화 진행

  1. CI 적용
    Trouble Shooting:
    TestContainer로 로컬 환경에서 잘 작동하였지만, GitLab Runner CI 단계에서 Docker socket을 공유하는 Gitlab Runner 컨테이너를 띄움, 그러나 테스트 컨테이너에서 코드 레벨에서 작성한 Redis TestContainer와 Mysql TestContainer가 뜨지 않는 문제 발생(Docker Out Of Docker)
    Solution:
    DIND(Docker in Docker)를 컨테이너를 사용해서 gitlab runner와 네트워크 브릿지로 연결하여 테스트 컨테이너를 연결함

기존 GitLab Runner

docker run --detach \
--name gitlab-runner \
--restart always \
--volume /srv/gitlab-runner/config:/etc/gitlab-runner \
--volume /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latest

변경 GitLab Runner

docker run -d \
--name gitlab-runner \
--restart always \
--network gitlab-runner-net \
--volume /srv/gitlab-runner/config:/etc/gitlab-runner \
--volume /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latest

Gitlab runner config

concurrent = 1
check_interval = 0
shutdown_timeout = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "gitlab-runner"
  url = "http://mentoring-gitlab.gabia.com/"
  id = 25
  token = "H-CtUsf2RGXfjMzk1syY"
  token_obtained_at = 2023-02-06T08:03:29Z
  token_expires_at = 0001-01-01T00:00:00Z
  executor = "docker"
  [runners.custom_build_dir]
  [runners.cache]
    MaxUploadedArchiveSize = 0
    [runners.cache.s3]
    [runners.cache.gcs]
    [runners.cache.azure]
  [runners.docker]
    tls_verify = false
    image = "docker:latest"
    privileged = true
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/certs/client", "/cache"]
    shm_size = 0

Docker in Docker Container

docker run –d --privileged \
 --name gitlab-dind \
 --restart always -p 2375:2375 \
 --network gitlab-runner-net \
 --volume /var/lib/docker \
 -e DOCKER_TLS_CERTDIR="" docker:dind



  1. Docker-Compose를 활용한 로컬 환경에서 실행 환경 통일하였습니다.
# docker-compose up -d
# docker-compose down -v
version: "3.8"                         # 파일 규격 버전
services: # 이 항목 밑에 실행하려는 컨테이너들을 정의
  b-shop-redis: # 서비스명
    container_name: b-shop-redis       # 컨테이너 명
    image: redis:6.2.7
    environment:
      - TZ=Asia/Seoul
    ports:
      - "6379:6379"
  b-shop-database:
    container_name: b-shop-database
    image: mysql:8.0.31
    environment:
      - MYSQL_DATABASE=bshop_db
      - MYSQL_ROOT_PASSWORD=1234
      - TZ=Asia/Seoul
    volumes:
      - ./database/config:/etc/mysql/conf.d
    ports:
      - "3306:3306"      # 접근 포트 설정 (컨테이너 외부:컨테이너 내부)

  1. 테스트 컨테이너를 통해 개발 환경과 운영환경과 테스트 환경을 동일하게 맞췄습니다.
 // test Containers
 testImplementation "org.testcontainers:junit-jupiter:1.17.6"
 testImplementation "org.testcontainers:mysql:1.17.6"
spring:
  profiles:
    active: test
  datasource:
    driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
    url: jdbc:tc:mysql:8.0.31://testDB
@SpringBootTest
@Testcontainers
public abstract class IntegrationTest {

    private static final String REDIS_VERSION = "redis:6.2.7";
    private static final int REDIS_PORT = 6379;

    @ClassRule
    static GenericContainer<?> REDIS_CONTAINER;

    static {
        REDIS_CONTAINER = new GenericContainer<>(REDIS_VERSION)
          .withExposedPorts(REDIS_PORT);

        REDIS_CONTAINER.start();
    }

    @DynamicPropertySource
    private static void properties(DynamicPropertyRegistry registry) {
        registry.add("spring.data.redis.host", REDIS_CONTAINER::getHost);
        registry.add("spring.data.redis.port", () -> REDIS_CONTAINER.getMappedPort(REDIS_PORT).toString());
    }
}



ERD 변경점

Version0.4

Version0.4

Version 0.6

Version0.6

  1. 옵션의 레벨을 정해서 여러 단계로 구현을 하려했지만, 현재 시간이 부족할 수 있는 상황이 발생할 수 있기에 옵션 레벨을 제거하고 설계하려합니다.
  2. 또한 Order와 Item의 다대다 관계를 푼 OrderItem 테이블에 Option이 연결되어있지 않는 피드백을 받아 1대 다로 연결하였습니다.
    이에 따라 주문 상세에서 주문에 해당한 Item과 함께 Option을 관리할 수 있습니다.
  3. Hiworks에서 사용자의 이름을 조회할 때 영어이름(한글이릉)으로 사용되어 이를 Parsing에서 사용할지 혹은 영어이름, 한글이름 구분해서 컬럼에 저장할지에 대해 논의하였지만 Member테이블에 이름컬럼으로 한번에 매핑한다고 결정되어
    varchar(15) ->varchar(100)으로 변경하였습니다.
  4. Item 테이블의 Description 컬럼을 원래 varchar(1000)으로 설정하였습니다. 이에 대해 Varchar(1000)을 하신 이유에 대해서 고민하였습니다.

첫 번쨰로는 Varchar(65535)로도 길이를 모두 표현 가능한데 TEXT를 사용하는 이유
한 테이블 내에 64바이트를 꽉 채운 가변길이 속성이 이상 존재하는 경우, TEXT를 사용해야 합니다.
모든 VARCHAR 타입은 한 레코드에서 65535 글자만 적재할 수 있기 때문(한 레코드 모든 글자의 합, MySQL InnoDB의 레코드 크기 제한사항) [현재 Mysql 8.0.31] TEXT 타입의 데이터는 다른 속성들과 별도로 다른 페이지에 저장되기 때문에 이러한 제한 사항이 없습니다.
varchar(65535)을 사용할 수도 있지만 name 속성이 가변 길이 공간을 침범하고 있어서 불가능할 수도 있습니다. 또한, 아이템 설명란에 길이 제한을 둔 것이 아니기에 TEXT 컬럼이 적합하다 생각하여 이로 변경하였습니다.


@Entity
public class Item extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
.
.
.
    @Column(columnDefinition = "varchar(255)", nullable = false)
    private String name;

    // @Column(columnDefinition = "varchar(1000)", nullable = false)
    // private String description;
    @Column(columnDefinition = "text", nullable = false)
    private String description;



추가된 규칙

Response Body

정상

// 정상
{
  "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJBY2Nlc3NUb2tlbiIsImlhdCI6MTY3NTYwMzU3OSwiZXhwIjoxNjc1NjA3MTc5LCJpZCI6MSwicm9sZSI6Ik5PUk1BTCJ9.nbe8qc36n4fsfvTEm3BflxF7kM7iwjYiGDeZN4MPFg4",
  "regiseterCompleted": false,
  "loginMemberResoponse": {
    "id": 1,
    "hiworksId": "normal01",
    "name": "normal01"
  }
}

에러

// 에러
{
  "msg": "에러에 대한 메시지"
}



작업

사전에 데이터를 집어 넣어서 로컬 환경에서 개발하였습니다.

@Profile("local")
@RequiredArgsConstructor
@Component
public class DataInit {

  @PostConstruct
  public void init() {
    Member member1 = Member.builder()
    .name("admin01")
    .email("[email protected]")
    .hiworksId("admin01")
    .phoneNumber("01000000001")
    .role(MemberRole.ADMIN)
    .grade(MemberGrade.DIAMOND)
    .build();
    // ...
    Category category1 = Category.builder()
   .name("카테고리1")
   .build();
    Category category2 = Category.builder()
    .name("카테고리2")
    .build();
    Category category3 = Category.builder()
    .name("카테고리3")
    .build();
    // ...
  }
}
  1. 상품(ITEM) CRUD

인증/ 인가 작업과 병행하여 일반 사용자와 관리는 적용하지 못하였지만,
상품을 등록하고, 변경하고, 조회하고, 삭제하는 기능을 구현하였습니다.

MapStruct 를 적용하는 과정에서 시행착오를 격는 중...

  • ex : 중첩된 Dto에 어떻게 값을 넣어줘야하나?
//Before
@Mapper(componentModel = "spring")
public interface ItemMapper {

    ItemMapper INSTANCE = Mappers.getMapper(ItemMapper.class);

    Item itemDtoToEntity(ItemDto itemDto);

    @Mapping(
            target = "categoryDto",
            expression = "java(CategoryMapper.INSTANCE.categoryToDto(item.getCategory()))")
    ItemDto itemToDto(Item item);
}
//After
@Mapper(componentModel = "spring")
public interface ItemMapper {

    ItemMapper INSTANCE = Mappers.getMapper(ItemMapper.class);

    Item itemDtoToEntity(ItemDto itemDto);

    @Mapping(source = "category", target = "categoryDto")
    ItemDto itemToDto(Item item);
}

~TODO : Option, Item image 관련 기능을 추가적으로 구현해야함


  1. 사용자 인증

Interceptor를 사용하여 사용자가 가지고 있는 accessToken을 파싱하여 Custom ArgumentResolver인
AuthArguementResolver를 등록하여 accessToken에 담겨 있는 사용자의 id와 role을 파싱하여
사용자 인증을 처리하였습니다.

또한 하이웍스 Oauth를 사용하여 프론트엔드에서 하이웍스 auth_code를 받아 이를 Spring 백엔드 서버에서 코드를 기반으로
하이웍스 accessToken을 얻어, 이를 통해 사용자의 프로필을 조회하는 것을 구현하였습니다.

Hiworks OAuth에서 제공하는 회원 정보는 전화 번호를 제공하지 않기에,
이를 따로 프론트에서 회원 가입란을 받아 회원 정보 수정을 하도록 회원 update 메서드를 구현하였습니다.

테스트 화면

  • image
  • image
  • image
  • image
  1. 주문 CRUD

주문 생성: 재고 확인, totalPrice 계산, 주문 후 재고 감소 로직 구현
주문 삭제(취소): 현재 주문 부분 취소를 지원하지 않고, 전체 취소만 구현 주문 취소: 취소 시 주문상태를 변경하고, 취소한 아이템에 대해 재고가 증가
Soft Delete로 처리하여 주문 취소된 항목도 조회 가능
주문 조회는 주문 단건 조회, 목록 조회 기능을 구현하였고, Paging기능을 추가

고민한 부분

public static OrderItem createOrderItem(final Item item, final Options options,
    final Orders orders, final int count) {
                  // ...
    options.decreaseStockQuantity(count);
    orders.calculateTotalPrice(orderItem, count);

    return orderItem;
  }

  public void cancel() {
    option.increaseStockQuantity(this.orderCount);
  }
  • 비즈니스로직을 entity에 둘지, Service단에 둘지에 대한 고민
  • ~TODO : 주문에 인증 로직 추가 필요

다음주 MILESTONE

  • 멘토님들 코드 리뷰에 대한 해결
  • 사용자 주문 정보 조회 :@summer님이 거의 다 해주신 거 조금 리팩토링 후 develop에 Merge
  • 상품 재고 관리 : @Becker
  • 상품 통계 정보 API : @Becker
  • 상품 주문 관리 : @Jenna
  • 장바구니 : @jaime
  • 모니터링 기능 조사 : @summer
Clone this wiki locally