본문 바로가기
프로젝트/미니

웹 미니 프로젝트 총평

by HWK 2023. 10. 3.

기술적 성취

1. CI / CD

Github Actions를 이용하여 workflow에 gradle.yml 파일을 생성하고 아래와 같이 작성한다.
Key 관련된 부분은 github Secrets에 작성해준다.
SSH_USER : ec2 유저 이름(빌드 관련 문서를 그대로 따라하셨으면 기본 설정으로 ubuntu가 있을 겁니다.)
SERVER_IP : ec2 public ip입니다.(private 아닙니다. 자세한건 빌드 문서 확인하시면 볼 수 있습니다.)
SSH_PRIVATE_KEY : ec2에서 받은 secret key 파일에 있는 코드 전문입니다. (파일 위치, 파일 이름이 아니고 해당 파일을 열었을 때 확인할 수 있는 코드입니다.)
라고 조장님이 알려주셨다

더보기
더보기
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle

name: Java CI with Gradle

on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]

permissions:
  contents: read

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'

    - name: Grant execute permission for gradlew
      run: chmod +x gradlew

    - name: Build with Gradle
      uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
      with:
        arguments: build
      #run : ./gradlew clean build

    - name: Upload artifact
      uses: actions/upload-artifact@v2
      with:
        name: testServer
        path: build/libs/team14project-0.0.1-SNAPSHOT.jar

  deploy:
    needs: build
    runs-on: ubuntu-latest

    steps:
    - name: Download artifact
      uses: actions/download-artifact@v2
      with:
        name: testServer
        path: build/libs/

    - name: Deploy to EC2
      run: |
        echo "${{ secrets.SSH_PRIVATE_KEY }}" > mykey.pem
        chmod 600 mykey.pem
        scp -i mykey.pem -o StrictHostKeyChecking=no build/libs/team14project-0.0.1-SNAPSHOT.jar ${{ secrets.SSH_USER }}@${{ secrets.SERVER_IP }}:/home/${{ secrets.SSH_USER }}/testServer.jar
        ssh -i mykey.pem -o StrictHostKeyChecking=no ${{ secrets.SSH_USER }}@${{ secrets.SERVER_IP }} "pgrep java | xargs kill -9; nohup java -jar /home/${{ secrets.SSH_USER }}/testServer.jar > app.log 2>&1 &"


2. WebSocket

내가 가장 손을 안댄 부분이기에 가장 열심히 학습 해야하는 부분이다.
기본적인 코드 흐름은 어렵지 않았다. 다만, 프론트엔드와의 교신이 가장 어려운 부분 같다.
중요한 기능은 createRoom, findAllRoom, removeRoom, sendMessage이다.
먼저 웹 소켓을 이용하기 위한 WebSocketConfig를 만든다.

@EnableWebSocket: 이 어노테이션은 웹 소켓 기능을 활성화하는데 사용된다.

WebSocketConfigurer: 이 인터페이스를 구현함으로써 웹 소켓 엔드포인트를 등록하는 메소드를 제공해야 한다.

registerWebSocketHandlers(WebSocketHandlerRegistry registry): 이 메소드는 웹 소켓 엔드포인트와 해당 엔드포인트를 처리할 핸들러를 등록한다. 여기에서 MyWebSocketHandler"ws/chat" 경로에 매핑하고, 허용된 origin("*")을 설정하여 웹 소켓 연결을 허용한다.

@RequiredArgsConstructor
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {


    private final MyWebSocketHandler myWebSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myWebSocketHandler, "ws/chat").setAllowedOrigins("*");
    }
}

아래 코드는 WebSocket 연결을 수신하고, 클라이언트로부터 수신한 텍스트 메시지를 ChatMessage 객체로 변환한 후, 이를 ChatService를 통해 채팅방 관련 로직을 처리하는 핸들러 클래스를 정의한다.

@Slf4j
@RequiredArgsConstructor
@Component
public class MyWebSocketHandler extends TextWebSocketHandler {
    private final ObjectMapper objectMapper;
    private final ChatService chatService;

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        System.out.println(payload);
        log.info("{}", payload);
        ChatMessage chatMessage = objectMapper.readValue(payload, ChatMessage.class);
        
        ChatRoom chatRoom = chatService.findRoomById(chatMessage.getRoomId());
        chatRoom.handlerActions(session, chatMessage, chatService);
    }
}

아래 코드는 채팅방(ChatRoom)을 나타내는 클래스로, WebSocket 세션을 관리하고 채팅 메시지를 처리하는 데 사용된다.

Set<WebSocketSession> sessions : WebSocket 세션을 저장하는 Set 컬렉션이다.
   여기에는 채팅방에 참여한 클라이언트의 WebSocket 세션들이 저장된다.

@Builder: Lombok 어노테이션으로, 빌더 패턴을 자동으로 생성한다. 즉 객체 생서과 초기화를 도와준다.

handlerActions(): 클라이언트의 WebSocket 세션과 채팅 메시지, 그리고 채팅 서비스를 받아와 채팅방의 동작을 처리하는 메서드이다. 주로 채팅 메시지를 전송하고 세션을 관리한다.

sendMessage(): 지정된 메시지를 채팅방에 참여한 모든 클라이언트에게 전송하는 메서드입니다.

   메시지 전송에 실패한 세션은 sessions에서 제거된다.

@Getter
public class ChatRoom {
    private Long roomId;
    private String name;
    private Set<WebSocketSession> sessions = new HashSet<>();

    @Builder
    public ChatRoom(Long roomId, String name) {
        this.roomId = roomId;
        this.name = name;
    }

    public void handlerActions(WebSocketSession session, ChatMessage chatMessage, ChatService chatService) {
        sessions.add(session);
        sendMessage(chatMessage, chatService);
        if (chatMessage.getType().equals(ChatMessage.MessageType.ORDER)
                ||chatMessage.getType().equals(ChatMessage.MessageType.DONE)) {
            //chatMessage.setMessage(chatMessage.getSender() +"님이 주문했숑");
            sendMessage(chatMessage, chatService);
        }
    }

    private <T> void sendMessage(T message, ChatService chatService) {
        Set<WebSocketSession> removesession = new HashSet<>();
        sessions.parallelStream()
                .forEach(session -> {
                    if (session.isOpen())chatService.sendMessage(session, message);
                    else removesession.add(session);
                });
        sessions.removeAll(removesession);
    }
}

이렇게 사용할 수 있게된 WebSocket를 service와 controller에서 이용해서 해당 기능을 구현한다.
아래는 프론트엔드의 사장님 측에서의 메인 코드들이다.
매장을 오픈하고, 웹소켓을 연결함으로써 송, 수신이 가능해지고,
송신을 하는 함수를 사용해 메세지 송신을 한다.
이 중 sendMessage와 connectWebSocket는 사용자 세션에서도 사용된다.

function storeOpen(){

    const message = `{
              "type":"ENTER",
              "roomId":"${ownerid}",
              "sender":"${name}",
              "message":"입장"
              }`
    console.log(message)
    //const message = messageInput.value;
    webSocket.send(message);

    console.log(1)
    messageInput.value = "";
    console.log(2)
    messageOutput.innerHTML += `<p>송신: ${message}</p>`;
    console.log(3)
  }



  // 웹 소켓 연결 함수
  function connectWebSocket() {
    const socketUrl = 'ws://43.201.67.57:8080/ws/chat';
    if (!webSocket && socketUrl.trim() !== "") {
      webSocket = new WebSocket(socketUrl);

      // 웹 소켓 연결 성공 시
      webSocket.onopen = (event) => {
        console.log("WebSocket 연결 성공!");
        connectionStatus.textContent = "연결됨"; // 연결 상태 표시 업데이트
      };

      // 웹 소켓 메시지 수신 시
      webSocket.onmessage = (event) => {
        const message = event.data;
        messageOutput.innerHTML += `<p>수신: ${message}</p>`;
      };

      // 웹 소켓 연결 종료 시
      webSocket.onclose = (event) => {
        console.log("WebSocket 연결 종료!");
        connectionStatus.textContent = "연결 종료"; // 연결 상태 표시 업데이트
      };
    }
  }

  // 메시지 전송 함수
  function sendMessage(userid,orderid) {

    if (webSocket) {
      const message = `{
        "type":"DONE",
        "roomId":"${userid}",
        "sender":"${name}",
        "message":"${orderid}번 주문이 완료되었습니다!"
      }`

      //const message = messageInput.value;
      webSocket.send(message);
      messageInput.value = "";
      messageOutput.innerHTML += `<p>송신: ${message}</p>`;
    }
    $.ajax({
      type: 'PUT',
      url: "/api/store/order/"+orderid,
      success: function (res){
        console.log(res)
      }
    })


3. Redis

많이 설명을 한 부분으로 아래 블로그에 작성해놓았다.
https://hwk99.tistory.com/103

 

35일차 - 효과적인 부분 문자열 검색(Full-Text, 캐시, Redis)

%Like%의 성능 관련 이슈에 대해서 보았다. %Like% 쿼리는 데이터베이스의 모든 레코드를 스캔하며 부분 문자열을 찾기 위해 일치하는 모든 데이터를 찾습니다. 이로 인해 대량의 데이터가 있는 테

hwk99.tistory.com

 

https://hwk99.tistory.com/104

 

36일차 - Cache에 Redis 적용시키기

기존 Cache는 시간 관련 기능이 적용되지 않았고, 이를 해결하기 위해 Redis를 도입했다. 가장 먼저 redis를 다운받았다. https://redis.io/download/ Download Redis You can download the last Redis source files here. For addit

hwk99.tistory.com

 

아쉬웠던 점

1. 기술적 아쉬움

  1. 너무 적은 기술적 성취
    4명이서 2주를 매달렸다고 믿을 수 없을 정도의 기술적 성취였다. 프로젝트 관리 및 협업 측면의 아쉬움과 겹치는 부분이긴 하나, 다음 프로젝트는 더욱 계획적으로, 그리고 열정적으로 임해 보도록 하자.
    이뤄야 했던 기술적 성취는 다음과 같다.
    • index 처리 : index 처리를 이용해 데이터 흐름을 더 효율적으로 관리할 수 있었다.
    • Refresh 토큰 적용 : 계획적이지 못한 측면을 잘 보여준다. 미리 구현을 해놓았어야 했다. 할줄은 알지만 너무 찝찝하고, 이번엔 header방식 말고 다른 방식으로 Refresh 토큰을 적용시키고 싶었다.
    • Docker 적용: 아마 시간상 부족했을 것이다. 하지만 Docker에 대해서 알아보는 시간이 있었으면 더 좋았을 것이다.
  2. 완벽하지 못한 예외처리
    예외처리가 되다 말다 하는 부분이 있었고, 예외처리 패키지를 구현해 놓고도 제대로 쓰지 못했다.
    다음에는 기능 구현 우선이 아닌 완벽한 기능 구현 우선, 또한 유지보수성이 높은 코딩을 해서, 어떤 기능을 고치는데
    두려움이 없도록 프로젝트를 진행하자

  3. N+1 문제 완벽 해결 실패
    @OneToOne 이후 @OneToMany로 연결되는 과정에서 문제를 겪었으나, 너무 프로젝트 마지막에 겪어서 일단 넘어갔다. 내 생각에는 index 처리를 해주면 해결될 문제로 보인다. 또한 원인은 역시 프로젝트 관리 및 협업 측면의 아쉬움과 겹친다.

  4. 이도 저도 아닌 테스트 코드
    좋은 계획이 있고, 테스트 코드에 대한 지식이 있는 상태로 접근했어야 했는데, 그러지 못해 결과적으로 이도 저도 아닌 프로젝트가 되어버렸다. 과감하게 놓아줬어야 그나마 발표라도 제대로 할 수 있었을 것 같다.
    테스트 코드 자체도 매우 조잡했다.. 완벽한 테스트 코드를 짜기 전까지는 어디 내밀지 않는게 좋을 것 같다.

  5. 프론트엔드 매우 부족
    운이 없었던 면도 있지만, 너무 이해에 오랜 기간이 걸렸고, 프로젝트를 망치는 주범중 하나가 되었다.
    그래도 이번에 한번 해봤으므로 다음에는 더 좋은 결과가 있을거라고 생각한다.

 

2. 프로젝트 관리 및 협업 측면 아쉬움

  1. 계획적이지 못했던 프로젝트
    일단 전체적인 큰 틀을 위주로 하나씩 하나씩 완성시켜 나가자는 계획을 세웠고, 이는 결과적으로 최악이였다.
    전체적인 큰 틀에는 시작과 중간 마무리가 반드시 들어가야 했고, 우리는 시작빼고는 모두 모호했다.
    결과적으로 가장 중요한 마무리를 잘 하지 못했고, 발표를 제대로 할 수 없었다. 우리의 2주를 날리는 순간이였다.
    중간 중간은 뭔가 짜놓긴 했지만, 프로젝트의 진행 순서, 반드시 해야할 일 등 제대로 정리해놓지 않아, 먼저 끝낸 인원들은 자신이 하고 싶은 코드를 짜고, 늦게 끝나는 인원들을 진도를 잘 나가지 못했다.
    중간 지점을 좀 더 많이 생각해 놓아야 했고, 계획이 바뀌면 바로바로 계획을 수정 해나가야 했다.
    계획의 중요성을 팀 프로젝트의 인원이 4명으로 늘어나니 확실하게 느낄 수 있었다.
    사실상 우리의 모든 아쉬운 점은 무계획성에서 비롯된 것이라고 볼 수 있다.
    앞으로는 시간이 오래걸려도 체계적인, 또 유지보수가 잘되는 계획을 세우도록 하자.

  2. 제대로 이뤄지지 못한 역할배분
    결국 불신에서 일어난 문제이다. 잘하는 팀원도 있고, 못하는 팀원도 있지만, 확실한 역할 배분 후,
    중간 점검 지점에서 계획만큼 프로젝트가 진행되지 못했다면, 그때서 계획을 수정하고 모자란 점을 채워나가야 했다.
    확실한 역할 배분이 있었으면, 모두 더 열정적으로 온 힘을 다해서 프로젝트를 진행 할 수 있었을 것이다.
    우리의 방식은 결과적으로 시간이 붕 뜨는 사람과, 좌절하고 힘들어하는 사람들이 늘 존재하고 있었다. 
    4명의 열정적인 신입 개발자들이 모였는데, 정말 말도안되는 상황이였다.

결론

앞으로는 계획을 중요시 하도록 하자. 두명이서는 서로 당기고 밀어줄 수 있으나, 그 이상부터는 한계가 있었다.
이번 프로젝트에서 배운 기술적 측면 뿐만 아니라, 아쉬웠던 점을 앞으로는 적극적으로 보완해 나가도록 하자.
또한 정신적으로도 많이 성장해야 한다는 것을 느꼈다. 너무나 실패에 약한 것 같다.
다음 프로젝트는 기간상 이번 프로젝트의 3배이며 작업량은 훨씬 많아야 할 것이다.
일주일동안 넋놓고 있었던 것을 반성하며, 또한 약 일주일의 휴식을 원동력으로 다음 프로젝트를 진행해보자

'프로젝트 > 미니' 카테고리의 다른 글

Eclipse, MS-SQL 연습용 프로젝트  (2) 2024.10.10
Cache에 Redis 적용시키기  (0) 2023.09.23
효과적인 부분 문자열 검색(Full-Text, 캐시, Redis)  (0) 2023.09.23
프론트엔드 개발  (0) 2023.09.22
코드 병합  (0) 2023.09.18