[졸프] 협업의 어려움과 방향성
과거 프로젝트, 현재
과거 프로젝트에서 느낀 협업의 어려운점은
- 서로 생각하는 방향이 다름
- 프론트엔드 화면 구성 견해 차이 발생
- 어떤 작업을 해야할지 모르는 순간 발생
- 이로 인해 개발 속도가 느려짐
- 코드 통합
- 특히 백엔드로 django를 사용했는데, migration 파일이 충돌했을때 힘들었다
- 코드 리뷰 후에 merge 했지만, 버그 발생
- 누군가 다른 팀원의 작업을 기다려야 하는 상황이 발생함
그래서 이번 졸업프로젝트에서는 똑같은 상황을 겪지 않기 위해 나름의 조치를 해보았다.
- 서로 생각하는 방향이 다름
-> 회의를 더 자주하고, 단기 계획을 세운다. - 코드 통합
-> Spring 사용, CI 구축 - 누군가 다른 팀원의 작업을 기다려야 하는 상황이 발생함
-> 도메인 주도 설계
주 2회 회의, Agile
문제점 해결
1주일 단위로 목표를 잡고 회의를 하니
일정에 긴장감이 있어서 루즈해지지 않았다.
그리고 서로 다른 생각을 계속해서 공유하니
금방 금방 합의할 수 있었다.
자세한 내용
- 일주일에 최소 1번 회의하며 진행상황 공유
-
특히 회의에서 팀의 목표를 계속해서 상기하고, 1주일 단위로 계획을 꼭 이룬다.
예를 들어
팀원 다같이 당장 발생한 문제를 어떻게 해결할 지 고민하고, 1주일 내로 해결했다.
특히 식당 정보 데이터베이스 구축 과정에서 계속해서 문제가 생겼는데- Api 키워드로 검색 시 중복 데이터 발생, 페이징 불가능
-> 공공데이터 정보 추가 후 식당 이름으로 검색 - 공공데이터 정보 추가 시 속도 문제, 예외처리 문제, 중간에 fail시 처음부터 다시 시작하는 문제
-> spring batch - Api 비용 문제
-> 대략적인 계산으로 현실성 검토 후 크롤링으로 전환
- Api 키워드로 검색 시 중복 데이터 발생, 페이징 불가능
버전은 4단계로 잡았다.
사실 해결 방법에 대해 고민하던 중 Agile 방법론을 알게 되어서 이를 도입하려 했다.
하지만 큰 계획을 먼저 잡기도 했고, 고객 요구사항을 반영하며 발전 시킨 것은 아니라서
Agile을 완벽하게 적용한 것은 아닌 것 같다.
그래도 현재까지는 이전 프로젝트에서 느낀 문제점들이 줄어들었다고 생각한다.
물론 열정 있는 팀원들 덕분이기도 하다.
후기
보통 칸반보드를 많이 이용한다고 해서 시도해 봤는데,
솔직히 안보게 되고
모든 팀원의 할일을 작성하고, 이슈로 만드는 시간이 너무 오래걸린다.
그래서 현업 친구에게 물어봤는데
백로그에 올리고 분배하는 일은 기획자/PM이 하지
개발자가 잘 하지 않는다고 한다.
개발자들끼리 하는 프로젝트 단계에서는
이슈 트래킹 정도가 적당한 것 같다.
CI 구축
문제점 해결
CI를 구축하니 서로 코드를 읽는게 수월했다.
특히 까먹고 지나간 부분을 잡아줘서 좋았다.
그리고 아래 사진처럼 최소한 빌드가 실패했는지 확인할 수 있어서
안심? 하고 병합할 수 있었다.
Spring 프레임워크를 사용해 마이그레이션 충돌은 없었다.
자세한 내용
수월한 코드리뷰를 위해 CI를 구축하는 여러가지 방법이 있었다.
Jenkins, Travis CI, Github Actions 등이 있는데
팀원 모두 Jenkins는 경험이 없었고
빠르게 문제를 해결하고 다음 단계로 넘어가는 팀 방향성에 맞게
Github Actions, Sonar Cloud를 사용했다.
아마 V2, V3 단계어서 Jenkins로 구축해볼 것 같다.
(사진처럼 시행착오를 좀 겪어서 완전 빠르지는 않았다)
Sonar Cloud를 사용해보니
테스트 코드를 강제할 수 있어서 좋았고
병합된 코드가 빌드 실패할 경우가 없다는 점에서 마음이 편했다.
특히 코드리뷰 시 어딴 부분을 중점적으로 봐야할지 알 수 있어서 좋았다.
디버깅용으로 까먹고 안지운 부분도 다 잡아준다.
도메인 주도 설계
Domain-Driven Design
문제점 해결
도메인 기준으로 접점을 최소화 했기 때문에
개발하며 겹치는 부분이 없어
서로 맡은 도메인에만 집중할 수 있었다.
자세한 내용 - 스키마 설계
개념은 인터넷에 많지만 (많으니 생략)
그래서 뭘해야 하는거지? 라는 생각이 들었다.
실질적으로 도입한 부분을 설명해보면
MSA로 전환했을 때 분리해야 할 service 기준으로 도메인을 나누었다.
그리고 그 기준으로 패키지를 나눴다.
현재 프로젝트에서는
유저, 식당, 추천, 투표
4가지 도메인으로 나눴다.
user domain처럼 애매한 부분도 있었다.
하지만 서버를 나눈다고 생각하면
user-service에서 로그인, 회원가입, 토큰 발급
gateway-service에서 인증, 인가
이렇게 나누어지는게 맞을 것 같아서
user, jwt를 나누지 않고 포함시켰다.
두번째로
pk가 아닌 식별자를 사용했다. (UUID라고 부르겠다)
그리고 이 식별자는 생성자에서 생성하도록 했다.
그리고 다른 도메인에서 도메인을 참조하면
예를 들어 투표 도메인에서 식당을 참조하면
pk가 아니라 UUID로 참조하도록 설계했다.
(3NF 만족하지 못하긴 한다)
@Entity
@Table(name = "restaurant")
@NoArgsConstructor(access = PROTECTED)
public class Restaurant extends BaseTimeEntity {
@Id @GeneratedValue(strategy = IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String restaurantId;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "category_id")
private Category category;
@Embedded
private Address address;
@Embedded
private OpenDataInformation openDataInformation;
@Embedded
private GoogleInformation googleInformation;
@Builder
public Restaurant(Category category, Address address, OpenDataInformation openDataInformation, GoogleInformation googleInformation) {
this.restaurantId = UUID.randomUUID().toString();
this.category = category;
this.address = address;
this.openDataInformation = openDataInformation;
this.googleInformation = googleInformation;
}
}
@Entity
@Table(name = "user_restaurant_vote")
public class UserRestaurantVote extends BaseTimeEntity {
@Id @GeneratedValue(strategy = IDENTITY)
private Long id;
@Column(nullable = false)
private String userId;
@Column(nullable = false)
private String restaurantId;
}
이는 느슨한 결합 때문인데
쉽게 말해 pk를 참조한 상태에서
식당과 투표 테이블을 다른 데이터베이스로 나눈다고 생각하면
당연히 에러가 발생할 것이다.
하지만 UUID로 참조하면
전혀 문제가 없을 것이다.
실제 값이 들어있는 상태에서도
테이블을 뜯기 쉬울 것이다.
식당과 식당 카테고리는 분리하지 않을 것 같아서
fk로 참조하도록 설계했다.
신기한 점은 똑똑한 인텔리제이가
Virtual foreign key를 만들어준다.
저 보라색 열쇠 버튼으로 켜고 끌 수 있다.
끄면 이런 모습이다.
자세한 내용 - 에러 코드 인터페이스
보통 예외는 Abstract class
에러 코드는 enum으로 관리한다.
그런데 이렇게 하면
에러 코드를 도메인별로 나눌 수 없어서
인터페이스를 만들어
도메인별로 관리하도록 설계했다.
public interface ErrorCode {
HttpStatus getHttpStatus();
String getMessage();
}
@RequiredArgsConstructor
@Getter
public enum UserErrorCode implements ErrorCode {
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다."),
SOCIAL_LOGIN_FAILED(HttpStatus.BAD_REQUEST, "소셜로그인에 실패했습니다."),
ILLEGAL_REGISTRATION_ID(HttpStatus.BAD_REQUEST, "허용되지 않는 소셜로그인입니다."),
INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다."),
INVALID_SIGNATURE(HttpStatus.UNAUTHORIZED, "유효하지 않은 서명입니다."),
REFRESH_EXPIRED(HttpStatus.UNAUTHORIZED, "로그인이 만료되었습니다. 다시 로그인해 주세요.");
private final HttpStatus httpStatus;
private final String message;
}
아마 다음 학기 소프트웨어 공학 수업을 들을 것 같은데
조금 더 구체적인 깨달음이 있겠지? 기대중
소감, 협업에 대한 개인적인 생각
이번에는 기술적으로 CI, Spring batch
그 외로 DDD, Agile을 접하게 되었는데
DDD, Agile 부분에서 당연하다고 생각이 드는 내용도 있었고
정돈되지 않은 깨달음도 있었다.
협업 할때마다 느끼는 것은
사공이 많으면 배가 산으로 가지만,
같은 곳을 바라보면 빠르다.
하지만 노를 저을줄 알아야 하고
여러가지 노를 다룰줄 알아야 하고
상황에 맞는 노를 선택할줄 알아야 하며
같은 목표를 향할때 따를줄 알아야 하고
정확한 목표를 향하자고 주장할줄도 알아야 한다.
이런 신입 사공은 얼마 없고
최소 노를 저을줄 아는 중고 사공은 많고
반대로 노를 젓는 신입때문에 PTSD도 생겼을 것이다.
그래서 요즘 신입 취업 시장이 어려운 것 아닐까?
도메인 주도 설계 외에도 AOP를 접했는데
로깅에 대한 부분이 가장 흥미로웠다.
특히 UUID를 부여해 사용자 요청을 식별하는 부분이
운영 환경에서 꼭 필요하겠다고 생각했다.
이부분은
따로 정리해 봐야겠다.
참고한 영상
https://www.youtube.com/watch?v=YlZ-NtEIsP8&t=1420s
Agile 찾아보다가 본 영상인데
무려 8년전 영상이지만
슈퍼 개발자가 트렌드이다, 모든걸 다 할줄 알아야 한다는
내용이 나온다 ㅋㅋㅋ
https://www.youtube.com/watch?v=sLG5n_pXWK0&t=1377s&pp=ygUQ7Yag7IqkIOyVoOyekOydvA%3D%3D
이건 DDD 영상인데 시작부터
당연한 내용 말할 것이니 나가셔도 좋다고 하신다.
난 재미있어서 끝까지 봤지만
도메인 주도 설계를 적용하며
그래서 뭘 해야하는거지? 의문이 든 나에게
원래 그런거야~ 답을 해주는 것 같아
약간 감사했다.