본문 바로가기
부트캠프TIL, WIL

팀 프로젝트에서 초기 개발 환경 설계와 JPA, 예외 관리까지: 완벽한 협업을 위한 체크리스트 [스프링부트]

by 우지uz 2024. 11. 11.

 

목차

  1. 팀 프로젝트 초기 개발 환경 설계
    1.1. 인증 설계와 인증/인가 방법
    1.2. 스프링 부트 환경 구성 및 의존성 관리
    1.3. 기획 및 요구사항 정의
    1.4. 엔티티 정의와 JPA 연관 관계 매핑
  2. 효율적인 팀 협업을 위한 규칙과 전략
    2.1. Git 협업 전략: 브랜치와 커밋 컨벤션
    2.2. 역할 분배와 개발 환경 이해도 체크
    2.3. 컨트롤러와 서비스 레이어 분리
  3. JPA Auditing을 활용한 공통 엔티티 설계
    3.1. BaseEntity를 활용한 생성/수정 정보 관리
    3.2. AuditorAware 구현을 통한 사용자 정보 자동 기록
  4. 전역 예외 처리와 일관된 API 응답 설계
    4.1. GlobalExceptionHandler를 활용한 예외 처리
    4.2. ExceptionStatus Enum으로 예외 정의
    4.3. 계층별 커스텀 예외 관리: ControllerException, ServiceException, RepositoryException
    4.4. API 응답 통일을 위한 ApiResponseUtil 설계
  5. 팀원 간 소통과 화합을 위한 체크리스트
    5.1. 설계와 구현에 대한 의사소통의 중요성
    5.2. 요구사항 불명확성 극복과 기술적 의사결정
    5.3. 문제 상황 설명 및 온라인 작업의 한계 극복
  6. 정리 및 마무리
    6.1. 초기 설계와 규칙이 전체 프로젝트에 미치는 영향
    6.2. 성공적인 팀 프로젝트를 위한 자세

1. 팀 프로젝트 초기 개발 환경 설계

1.1 인증  설계와  인증 / 인가 방법

  • 프로젝트 초기 단계에서 인증과 인가 방식은 필수적으로 논의해야 합니다. 모든 사람이 시큐리티나 인증/인가에 대해서 아는 것이 아니기도 하고, 내가 구현한 인증 방식이 다른 사람과 다를 가능성이 충분히 있기 때문입니다. 
  • Postman과 같은 툴을 이용하여 Header에 Authorization: Bearer {{Token}}을 설정하는 방법도 모를 수 있습니다. 안내 해주는 것이 팀원들의 체력을 안보하는데에 좋을 것 같습니다.
  • Spring Security의 SecurityContextHolder를 통해 인증 객체를 꺼내오는 방법을 공유합니다. 

1.2 스프링 부트 환경 구성 및 의존성 관리

  • 프로젝트에 필요한 주요 라이브러리(Spring Security, JPA, QueryDSL 등)와 프레임워크를 정의하고 초기 build.gradle 을 세팅합니다.

1.3 기획 및 요구사항 정의

  • 요구사항 분석을 통해 주요 엔티티를 도출하고, 서비스 간의 인터페이스 설계에 대한 논의가 필요합니다.

대부분의 기획서에 구체적인 설계 방법에 대한 것은 정의되어 있지 않은 경우가 많기 때문에 
알아서 설계하고 구현해야 하는 상황이 생긴다. 그렇기 때문에, 구체적인 설계 방향에 대해서는 직접 설계 해보시라. 

저희는 그 중 대표적으로 
주문 - 결제 내역에 대한 부분을 리뷰 엔터티와 연결할 때 어떻게 설계 하는지에 대해 의견이 나뉘었습니다. 

배달앱과 같은 경우는 주문의 상태에 따라 결제내역도 어떻게 처리될 지 달라지기 때문에, 
리뷰 같은 경우는 Order 의 상태에 따라서 로직을 처리하는게 PaymentHistory 의 상태에 따라 리뷰를 작성하는 것 보다 낫다고
의견이 모아졌습니다. 

만약에 배달앱과 다른 애플리케이션 서비스 앱의
결제내역 데이터가 완전히 100% 결제가 완료되었다는 전제하에서 결제내역이 남게되고
그 결제내역을 기준으로 리뷰를 작성해야 한다면
리뷰에 결제내역 id 를 외래키로 넣어주면 될 것 같다는 생각도 했습니다. 

1.4 엔티티 정의와 JPA 연관 관계 매핑

저는 JPA 의 가장 주요한 역할이 두개라고 한다면 
첫번째는 ORM 맵핑, 두번째는 영속성 컨텍스트의 등장과 그 내에서 1차 캐시 활용이라고 생각합니다. 
그 중에서도 저희가 각 기능구현이 들어가기 전에 가장 중요하게 다뤘던 부분이 JPA 를 통한 엔티티 설계였습니다. 

특히나 팀원 분들 중에서 ToOne 관계와 ToMany 관계에서 디폴트 FetchType 이 뭔지 모르는 분이 계셨고
지연 로딩과 즉시 로딩의 차이점을 설명 드리면서, ToOne 관계에서 명확한 의도 목적으로 즉시 로딩을 거는 것에 대해 확실하지 않으면 

FetchType.LAZY 를 걸어주고, N+1 문제에서 Fetch Join 을 통해서 즉시 모든 데이터를 1차 캐시를 활용해서 가져오는 
방법을 사용하도록 권장 드렸습니다. 

@ManyToOne(fetch = FetchType.LAZY)

엔티티 정의 및 연관 관계 설정 시 체크리스트. 

  • 각 엔티티의 연관 관계를 설계하고, 디렉터리 구조를 논의합니다. 
    ex ) To One 관계에서 fetchType 을 Lazy 로 설정한 후에 즉시 로딩과 지연 로딩의 차이점을 공유한다.
  • N+1 문제를 해결하기 위해 Fetch Join 사용과 같은 JPA 경험과 이유에 대해서 공유한다.

2. 효율적인 팀 협업을 위한 규칙과 전략

Git Branch 전략과 Commit Convention 에 대해 이야기하면서 
처음부터 각자가 활용해오던 Git 전략이 다르며, 어떻게 Git Repository 를 관리하는 것이 효과적인가? 에 대해서 이야기를 나누었습니다. 

결론적으로 우리 팀의 Organization 을 만들고, 그 내에 Repository 저장소를 만들며 
팀원들은 Fork 기능을 통해서 각자의 개인 Repository 저장소로 가져와 

개인 브랜치에 작업을 하고, 개인 Repository 저장소로 Push 한 다음 

팀의 Repository 저장소에 Pull Request 를 날리어,
각자의 PR 에 대해서 서로서로 리뷰해주며 혹시나 놓친건 없는지 ? 
자기가 더 잘 아는 것에 대해서, 이렇게 코드를 작성한다거나 라이브러리를 활용하면 좋다던지 ? 
공유할 수 있어서 좋았습니다. 

2.1 Git 협업 전략: 브랜치와 커밋 컨벤션

  • 브랜치 전략은 main, develop, feature 브랜치를 명확히 구분하며, 커밋 메시지 컨벤션은 [타입: 설명] 형식으로 통일합니다.

깃 허브 이슈, PR 을 통해서 적극적으로 코멘트를 달고 소통하는데에 노력을 기울입니다.

2.2 역할 분배와 개발 환경 이해도 체크

  • 팀원별로 QueryDSL, AOP, JPA에 대한 이해도, 배포경험과 MSA의 수준을 파악하고 적절한 역할분배와 기술적 난이도를 결정합니다.

2.3 컨트롤러와 서비스 레이어 분리

  • 트랜잭션의 readOnly 여부 설정과 각 서비스의 책임을 명확히 정의합니다. AOP 의 권한 체크를 사용해서 컨트롤러단에서 서비스단으로 권한 책임을 넘길 필요가 없는 경우에 넘기지 않는다. (서버 리소스 최적화)

3. JPA Auditing을 활용한 공통 엔티티 설계

3.1 BaseEntity를 활용한 생성/수정 정보 관리

  • 반복적으로 사용되는 생성일, 생성자, 수정일, 수정자 등의 정보를 BaseEntity로 추상화하여 공통 관리를 합니다.

3.2 AuditorAware 구현을 통한 사용자 정보 자동 기록

  • Spring Security를 활용하여 현재 사용자 정보를 createdBy, updatedBy 필드에 자동으로 기록합니다.
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
    @CreatedDate
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;

    @CreatedBy
    @Column(name = "created_by", updatable = false)
    private String createdBy;

    @LastModifiedDate
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;

    @LastModifiedBy
    @Column(name = "updated_by")
    private String updatedBy;

    @Column(name = "deleted_at")
    private LocalDateTime deletedAt;

    @Column(name = "deleted_by")
    private String deletedBy;
}

팀 프로젝트에서 각 엔티티마다 반복적으로 들어가는 생성 및 수정 정보를 관리하기 위해 BaseEntity 클래스를 만들어 상속 구조로 사용하는 것이 효율적입니다. 이 예제에서는 Auditing 기능을 활용하여 자동으로 생성일, 생성자, 수정일, 수정자를 기록하고, 논리 삭제를 위한 필드도 함께 관리하고 있습니다. 요구사항처럼 삭제 기능과 같은 경우는, 소프트 Delete 로 구현하여, 완전히 삭제되는 것이 아니라 삭제가 누군가에 의해 되었음을 deletedBy 를 통해 기록하도록 되었습니다. 

코드 설명

  1. @MappedSuperclass
    BaseEntity는 실제 테이블로 생성되지 않고 상속을 통해 필드를 제공하기 위해 @MappedSuperclass 어노테이션을 사용했습니다.
  2. @EntityListeners(AuditingEntityListener.class)
    생성일과 수정일 등을 자동으로 기록하기 위해 JPA의 Auditing 기능을 활성화하는 AuditingEntityListener를 추가했습니다.
  3. Auditing 필드
    • @CreatedDate, @LastModifiedDate: 각각 생성일과 최종 수정일을 자동으로 기록합니다.
    • @CreatedBy, @LastModifiedBy: 각각 생성자와 최종 수정자를 기록합니다. 이 필드들은 Spring Security 등의 인증 정보를 기반으로 자동 설정할 수 있습니다.
  4. 논리 삭제 필드
    • deletedAt, deletedBy 필드를 추가하여 데이터가 실제로 삭제되지 않고 논리적으로 삭제될 때 삭제일과 삭제자를 기록할 수 있습니다. 이를 통해 데이터를 복원하거나, 삭제된 데이터에 대한 로그를 남길 수 있습니다.

필드 설명

  • createdAt, createdBy: 데이터가 최초 생성된 일시와 생성자 ID를 저장합니다.
  • updatedAt, updatedBy: 데이터가 마지막으로 수정된 일시와 수정자 ID를 저장합니다.
  • deletedAt, deletedBy: 데이터가 삭제된 일시와 삭제자 ID를 저장하여 논리 삭제를 관리합니다.

여기서 By 들어가는 사용자는 시큐리티 컨텍스트 홀더에서 가져온 인증 객체의 User id(email) 과 같은 것이기에 
컨텍스트 홀더에 담기지 않는 더미데이터와 같은 경우에는 생성자, 수정자, 삭제자가 들어가지 않습니다. 

인증 객체로 부터 회원의 id, role, email 등 들어갈 수 있기에, 보안상 넣고 싶지 않다거나 이메일은 굳이 UserDetails 에 넣지 않는 것이 좋겠다. 라는 보안 설계에 대한 부분을 팀원분과 소통해봐야 합니다. 

저와같은 경우에는 JWT Token 내에 유저의 PK, Role 역할값만을 설정해두었기 때문에, 유저의 이메일값까지 Authentication 에 넣는걸 원하지 않았습니다. 그에 대한 내용은 아래에 설명해두었습니다. 


AuditorAware 구현을 통한 사용자 정보 자동 기록


엔티티의 생성자 및 수정자를 자동으로 기록하기 위해 AuditorAware 인터페이스를 구현하여 현재 인증된 사용자 정보를 제공합니다. 이 구현체는 Spring Security의 인증 정보를 사용하여 현재 로그인한 사용자의 이름 또는 ID를 반환하도록 설정되어 있습니다.

코드 설명

  1. AuditorAware 인터페이스 구현
    AuditorAwareImpl 클래스는 AuditorAware 인터페이스를 구현하여 현재 사용자의 정보를 Optional로 반환합니다. @Component 어노테이션을 사용하여 Spring이 자동으로 빈으로 등록할 수 있게 합니다.
  2. getCurrentAuditor 메서드
    • SecurityContextHolder를 통해 현재 인증된 사용자의 Authentication 객체를 가져옵니다.
    • Authentication 객체가 null이 아니고, 사용자가 인증된 상태인지 확인합니다.
    • 인증된 상태라면, authentication.getPrincipal()을 통해 현재 인증된 User 객체를 가져오고, user.getUsername()을 반환합니다.
    • 이 값이 엔티티의 createdBy 및 updatedBy 필드에 자동으로 할당됩니다.
  3. TODO: User 객체의 변경에 따른 수정 필요
    authentication.getPrincipal()에서 반환되는 객체가 User 클래스이므로, 만약 User 객체의 구조가 변경될 경우 이에 맞게 코드를 수정해야 합니다. 예를 들어, User 객체에 사용자 이름이 아닌 다른 식별자를 사용한다면, 그에 맞춰 user.getUsername() 부분을 변경해야 합니다.
@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {

    private final AuthenticatedUserDto authenticatedUserDto;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of((GrantedAuthority) () -> authenticatedUserDto.getMemberRole().toString());
    }

    @Override
    public String getPassword() {
        return authenticatedUserDto.getPassword();
    }

    @Override
    public String getUsername() {
        return authenticatedUserDto.getEmail();
    }

    public UUID getMemberId() { return authenticatedUserDto.getMemberId(); }

    public MemberRole getMemberRole(){
        return authenticatedUserDto.getMemberRole();
    }

    @Override
    public boolean isAccountNonExpired() {
        return authenticatedUserDto.isActive();
    }

    @Override
    public boolean isAccountNonLocked() {
        return authenticatedUserDto.isActive();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return authenticatedUserDto.isActive();
    }

    @Override
    public boolean isEnabled() {
        return authenticatedUserDto.isActive();
    }
}
  • 4. Optional.empty() 반환
    인증된 사용자 정보가 없을 경우 Optional.empty()를 반환합니다. 이는 시스템 내에서 사용자 정보가 없는 경우에 대비한 기본값으로, Auditing 필드에 null 값이 저장될 수 있도록 합니다.

 


4. 전역 예외 처리와 일관된 API 응답 설계


4.1 GlobalExceptionHandler를 활용한 예외 처리

예외 처리 클래스 설명

GlobalExceptionHandler는 @RestControllerAdvice 어노테이션을 사용하여 전역 예외 핸들러로 등록되었습니다. 이를 통해 컨트롤러, 서비스, 리포지토리 계층에서 발생하는 주요 예외들을 일관된 형식으로 응답할 수 있습니다.

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    @ExceptionHandler(ControllerException.class)
    public ResponseEntity<ApiResult<ApiError>> handleControllerException(ControllerException e) {
        log.error("ControllerException: {}", e.getMessage());
        return new ResponseEntity<>(ApiResponseUtil.error(e.getMessage()), new HttpHeaders(), e.getStatus());
    }

    @ExceptionHandler(ServiceException.class)
    public ResponseEntity<ApiResult<ApiError>> handleServiceException(ServiceException e) {
        log.error("ServiceException: {}", e.getMessage());
        return new ResponseEntity<>(ApiResponseUtil.error(e.getMessage()), new HttpHeaders(), e.getStatus());
    }

    @ExceptionHandler(RepositoryException.class)
    public ResponseEntity<ApiResult<ApiError>> handleRepositoryException(RepositoryException e) {
        log.error("RepositoryException: {}", e.getMessage());
        return new ResponseEntity<>(ApiResponseUtil.error(e.getMessage()), new HttpHeaders(), e.getStatus());
    }
}

 

  • handleControllerException, handleServiceException, handleRepositoryException: 각 계층의 예외를 처리하는 메서드로, 발생한 예외에 따라 서로 다른 핸들러가 작동합니다.
  • 예외 발생 시, ApiResponseUtil을 사용해 일관된 형식의 에러 응답을 반환하며, 예외 메시지와 HTTP 상태 코드를 포함합니다.
  • @RestControllerAdvice를 사용하여 컨트롤러, 서비스, 리포지토리에서 발생한 예외를 전역적으로 처리합니다.
  • 이는 컨트롤러 단에서 예외에 대한 처리이기에, 예외처리 같은 경우 Filter 단에서 작동하지 않을 가능성이 높습니다. 

4.2 ExceptionStatus Enum으로 예외 정의

 ExceptionStatus (Enum)

  • ExceptionStatus는 다양한 예외 상황을 HTTP 상태 코드와 메시지로 정의한 열거형입니다. 이 열거형은 커스텀 예외에서 사용되며, 예외의 상태와 메시지를 명확하게 구분할 수 있게 합니다.
public enum ExceptionStatus {
    PRODUCT_NOT_FOUND(HttpStatus.NOT_FOUND, "P001", "Product not found"),
    PRODUCT_ALREADY_EXISTS(HttpStatus.CONFLICT, "P002", "Product already exists"),
    INVALID_REFRESH_TOKEN(HttpStatus.FORBIDDEN, "j001", "유효하지 않은 리프레시 토큰입니다."),
    ACCESS_DENIED(HttpStatus.FORBIDDEN, "r001", "권한이 없습니다."),
    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "s001", "서버 에러입니다.");

    private final int status;
    private final String customCode;
    private final String message;
    private final String err;
}
  • 각 예외는 HttpStatus, customCode, message, err를 가지며, 이를 통해 예외의 세부 정보를 체계적으로 관리할 수 있습니다.
  • 다양한 예외 상황을 HTTP 상태 코드와 메시지로 구체적으로 정의하여 관리합니다.

 

4.3 계층별 커스텀 예외 관리

DefaultException 및 계층별 예외 (ControllerException, ServiceException, RepositoryException)

DefaultException 클래스는 모든 커스텀 예외의 기본 클래스입니다. 이 클래스는 ExceptionStatus를 통해 예외의 상태 코드와 메시지를 정의할 수 있도록 합니다.

public class DefaultException extends RuntimeException {
    private final ExceptionStatus exceptionStatus;

    public DefaultException(ExceptionStatus exceptionStatus) {
        this.exceptionStatus = exceptionStatus;
    }

    @Override
    public String getMessage() {
        return exceptionStatus.getMessage();
    }

    public int getStatus() {
        return exceptionStatus.getStatus();
    }
}
  • ControllerException, ServiceException, RepositoryException은 각 계층에서 발생하는 예외를 관리하기 위해 DefaultException을 상속받아 정의되었습니다. 이렇게 계층별로 구분된 예외를 통해 문제 발생 위치를 명확히 알 수 있습니다.

예외 처리 예시

// Controller에서 예외 발생
throw new ControllerException(ExceptionStatus.PRODUCT_NOT_FOUND);

// Service에서 예외 발생
throw new ServiceException(ExceptionStatus.INVALID_REFRESH_TOKEN);

// Repository에서 예외 발생
throw new RepositoryException(ExceptionStatus.INTERNAL_SERVER_ERROR);
  • ControllerException, ServiceException, RepositoryException을 분리하여 예외의 발생 위치를 명확히 알 수 있도록 설계합니다.

 

4.4 API 응답 통일을 위한 ApiResponseUtil 설계

  • GlobalExceptionHandler는 ApiResponseUtil을 활용하여 예외 발생 시 일관된 응답을 반환합니다. 이를 통해 클라이언트는 성공 여부와 에러 메시지를 예측 가능하고 구조화된 형식으로 수신할 수 있습니다.
return new ResponseEntity<>(ApiResponseUtil.error(e.getMessage()), new HttpHeaders(), e.getStatus());
  • 성공 여부와 에러 메시지를 일관된 구조로 반환하여 클라이언트가 쉽게 이해할 수 있도록 설계합니다.

프로젝트에서 예외 처리를 일관되게 관리하기 위해 Exception 패키지를 활용한 예외 관리 구조를 설계했습니다. 이를 통해 예외를 보다 구조적으로 관리하고, 각 계층의 예외를 GlobalExceptionHandler에서 통합적으로 처리하여 API의 안정성과 가독성을 높였습니다.

 


디렉토리 구조

디렉토리는 global이라는 상위 폴더 아래에서 다양한 하위 패키지(base, config, exception, util)로 나누어져 있으며, 예외 관리는 exception 패키지 안에서 이루어집니다.

  • base: AuditorAwareImpl와 BaseEntity 등의 공통 엔티티 및 감사 로직 관리.
  • config: Hibernate와 Spring MVC의 설정 파일 관리.
  • exception: 다양한 예외 클래스를 포함하며, 각 계층별로 구분된 예외와 전역 예외 핸들러를 관리.
  • util: 공통적인 API 응답을 위한 유틸리티 클래스인 ApiResponseUtil을 관리.

2. 디렉토리별 설명

ai

  • 도메인 역할: AI와 관련된 로직(예: 추천, 분석, 상품 내용에 대한 설명 작성)을 관리합니다.
  • 하위 디렉토리:
    • /dto: AI 요청 및 응답에 필요한 데이터 전송 객체.
    • /repository: AI와 관련된 데이터 처리 로직을 담당.
    • /entity: AI에서 필요한 엔터티 데이터 구조 정의.
    • /controller: AI 관련 요청을 처리하는 REST API 엔드포인트.
    • /model: 머신러닝 모델이나 AI 로직에 필요한 클래스 정의.
    • /service: AI 비즈니스 로직(분석, 예측)을 처리.

members, order, Review, Profile, Payment, Store, etc ...

  • 도메인 역할: 회원(Member) 관리와 관련된 기능을 처리합니다.
  • 하위 디렉토리:
    • /dto: 회원 가입, 로그인 등 요청 및 응답 객체.
    • /repository: 회원 데이터를 저장하고 관리하는 DAO 계층.
    • /entity: 회원의 속성을 담은 엔터티 클래스.
    • /controller: 회원 관련 요청(예: 가입, 로그인)을 처리하는 API 엔드포인트.
    • /service: 회원 비즈니스 로직(인증, 정보 업데이트 등)을 처리.

security

  • 도메인 역할: 프로젝트의 보안(Security) 기능을 담당합니다.
  • 하위 디렉토리:
    • /config: Spring Security 설정 클래스(예: 인증/인가 설정).
    • /jwts: JWT 생성, 파싱, 유효성 검증 관련 로직.
    • /dto: 보안 관련 데이터 전송 객체(예: 인증 요청/응답).
    • /filters: 인증/인가를 위한 커스텀 필터.
    • /repository: 보안 관련 데이터 저장소(예: RefreshToken 관리).
    • /entity: 보안 관련 엔티티(예: 인증 정보).
    • /utils: 보안 관련 유틸리티 클래스(JWT 유효성 검사 등).
    • /service: 인증/인가와 관련된 비즈니스 로직(예: 로그인, 토큰 관리).
    • /authentication: 인증 관련 로직 처리.

global

  • 도메인 역할: 전역적으로 사용되는 공통 모듈과 설정을 관리합니다.
  • 하위 디렉토리:
    • /util: 프로젝트 전반에 걸쳐 사용하는 유틸리티 클래스.
    • /config: 전역 설정 파일(CORS 설정, Resource Handler, ObjectMapper, RestTemplate, Email 설정 등).
    • /storage: 파일 저장소나 외부 스토리지와 관련된 로직.
    • /exception: 전역 예외 처리 클래스 및 커스텀 예외 정의.
    • /base: 공통 엔티티(BaseEntity)와 같은 기본 클래스.

5. 팀원 간 소통과 화합을 위한 체크리스트

5.1 설계와 구현에 대한 의사소통의 중요성

  • 설계와 구현 과정에서 의견 차이가 있을 경우 감정적으로 반응하기보다는, 객관적인 근거와 논리를 바탕으로 적극적으로 논의합니다.
  • Pull Request(PR) 및 코멘트 기능을 적극 활용하여 자신의 코드를 설명하고, 팀원들의 피드백을 반영합니다.
  • 기술적 의사소통이 어려운 경우, 캠프 내 튜터님이나 관련 기술 전문가의 도움을 요청합니다.
  • 팀원 간 코드 리뷰 시 비판이 아닌 개선을 위한 태도로 접근합니다.

5.2 요구사항 불명확성 극복과 기술적 의사결정

  • 구체적인 요구사항이 명확하지 않을 경우:
    • 먼저 팀원들과 함께 요구사항을 분석하여 이해도를 높입니다.
    • 요구사항이 명확하지 않더라도, 가능한 선택지와 기술적 타당성을 바탕으로 협력하여 가장 적합한 결정을 내립니다.
  • 의사결정이 어려운 경우에는 가능한 해결 방향을 문서화하여 팀원들과 공유하고, 합리적인 방안을 도출합니다.
  • 의사결정의 과정을 투명하게 기록하여, 추후 동일한 문제가 발생했을 때 참고할 수 있도록 합니다.

5.3 문제 상황 설명 및 온라인 작업의 한계 극복

  • 문제 상황 발생 시 팀원들에게 명확하고 간결하게 문제를 설명합니다:
    • 문제의 배경: 문제가 발생한 맥락을 설명.
    • 현상: 문제가 어떻게 나타나는지 기술.
    • 시도한 해결 방법: 이미 시도한 방안을 공유하여 불필요한 중복 작업을 피합니다.
  • 혼자 문제를 해결하려고 하거나 숨기는 습관을 버리지 않고, 팀원들과 적극적으로 협업합니다.
  • 온라인 작업 환경에서 발생하는 소통 한계를 극복하기 위해:
    • **협업 툴(Slack, Notion, Jira 등)**을 효과적으로 사용합니다.
    • 문제 발생 시 화상 회의스크린 쉐어링을 활용하여 팀원들과 함께 해결 방법을 논의합니다.

추가 체크리스트

  1. 커뮤니케이션 관련
    • 정기적인 회의에서 각자의 진행 상황을 공유합니다.
    • 어려운 점이나 도움이 필요한 부분은 주저하지 말고 팀원들에게 요청합니다.
    • 타인의 의견을 경청하며, 상대방의 입장을 이해하려고 노력합니다.
  2. 문서화
    • 설계, 요구사항, 의사결정, 문제 해결 과정을 기록하여 팀원들과 공유합니다.
    • GitHub Wiki, Notion 등 협업 도구를 통해 필요한 정보를 투명하게 관리합니다.
  3. 팀워크 향상
    • 팀원들 간의 신뢰를 쌓기 위해 서로 격려와 지지를 표현합니다.
    • 비난보다는 해결책을 제안하는 태도로 대화합니다.
    • 갈등이 발생했을 때는 중립적인 제3자의 의견을 구하여 해결합니다.
  4. 업무 분담
    • 각 팀원의 역할과 책임을 명확히 정의합니다.
    • 업무 분배가 공정하게 이루어지고 있는지 정기적으로 점검합니다.
  5. 비상 상황 대비
    • 팀원 중 누군가가 예상치 못한 이유로 작업을 진행하지 못할 경우 대비하여, 중요한 작업의 백업 담당자를 지정합니다.
    • 주요 업무의 진행 상황과 맥락을 문서화하여, 인계가 쉽도록 합니다.

6. 정리 및 마무리

6.1 초기 설계와 규칙이 전체 프로젝트에 미치는 영향

  • 초기 설계와 팀 규칙은 코드의 질과 협업의 효율성에 직접적으로 영향을 미칩니다.
  • 컨트롤러에 서비스단에서의 

6.2 성공적인 팀 프로젝트를 위한 자세

  • 과거의 경험에만 의존하지 않고, 현재 팀원들과 신뢰를 쌓으며 프로젝트를 진행하는 것이 중요합니다. 개인적으로 자신의 능력을 높게 평가할 수 있지만, 객관적으로는 모든 사람에게 부족한 부분이 있을 수 있습니다.
  • 다른 사람을 평가하거나 과소평가하기보다는, 팀의 목표와 결과물에 집중하여 협력하는 자세가 필요합니다. 서로 다른 경험과 관점을 가진 팀원들이 조화를 이루면 더 나은 결과물을 만들어낼 수 있습니다.
  • 팀원 간의 소통에서는 상대방을 존중하는 태도가 중요합니다. 지나치게 높은 기준을 요구하거나, 자신의 의견만을 고집할 경우 갈등이 발생할 수 있습니다. 코드 품질을 높이는 것은 중요하지만, 팀 전체의 역량과 프로젝트의 현실적인 여건을 고려하여 균형을 맞추는 것이 필요합니다.

최대한 객관적으로 상황을 바라보며, 논리적으로 설득하고 설계와 구현에 있어 건설적인 논의를 통해 팀의 성공에 기여할 수 있어야 합니다.
팀원들간의 기술적인 결정이 어려운 경우에는 튜터님들이나 다른 사람과의 논의를 통해 천천히 결정해보는 것도 , 기술적인 질에 큰 영향을 끼친다고 생각합니다. 


 

위와같이 팀 프로젝트를 빌딩하는 과정에서 저희 CORE 팀이 겪었던 의사 소통과정과 개발 규칙, 컨벤션에 대해서 이야기 해보았고 
그 과정에서 서로가 아는 것을 나누고 잘 소통하여 서로서로 잘 성장할 수 있었습니다 ! 

감사합니다.