[크래프톤 정글 3기] 1/9(화) TIL

ClassBinu·2024년 1월 9일
0

크래프톤 정글 3기 TIL

목록 보기
84/120

09:06 입실
눈이 많이 내린다.
오늘 개인 프로젝트 거의 마지막 날인데, 테스트 코드와 도커 기반 CI/CD까지 할 수 있을까..?

Array.prototype.some()

likes.some((like: any) => like.user.id === decodedToken.sub)}

some을 해당 배열을 순회하면서 하나라도 조건에 맞다면 true를 반환하고 탐색을 종료한다. 즉, 모든 배열을 탐색하지 않으므로 효율적이다.

좋아요/싫어요 로딩

좋아요/싫어요가 서버 통신 시간 때문에 너무 느림.
(2~3초)

클라이언트단에서 임시로 작업 후 이상이 있으면 다시 복구시키는 방식으로 코드 수정

옵티미스틱 UI 업데이트

서버 응답의 지연으로 인해 사용자 경험(UX)이 저하되는 문제를 해결하기 위해 클라이언트 측에서 먼저 UI를 업데이트하고, 이후 서버 응답을 받아 실제 데이터를 동기화

용자는 요청이 실시간으로 처리되는 것처럼 느낄 수 있음.

클라이언트쪽에서 락을 걸어서 동시성 문제 해결함.

  const handleLike = async () => {
    if (pendingLike) {
      Swal.fire({
        icon: "info",
        title: "'좋아요'를 처리하고 있어요.",
        text: "잠시 후 다시 시도해 주세요.",
      });
      return;
    }
    setPendingLike(true);

    if (likes.some((like) => like.user.id === decodedToken.sub)) {
      console.log("좋아요 취소");
      setLikes(likes.filter((like) => like.user.id !== decodedToken.sub));
    } else {
      console.log("좋아요");
      setLikes([...likes, { user: { id: decodedToken.sub } }]);
    }

    try {
      const accessToken = await getAccessTokenAndValidate();
      await fetch(
        `${process.env.NEXT_PUBLIC_SERVER_API}/posts/${params.uuid}/likes`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${accessToken}`,
          },
        }
      );
      await fetchLikesAndDislikes();
    } catch (error) {
      console.error(error);
    } finally {
      setPendingLike(false);
    }
  };

서버에서 큐로 순차적으로 처리하는 방식도 있음. 동시성 문제는 생각보다 복잡한 문제다. 이런 프론트엔드와 백엔드에서 모두 고려해야 할 문제임.

현재는 클라이언트에서 락 방식으로 막아놨지만 이건 임시방편임. 결국 서버에서 최종적으로 트랜잭션 단위로 묶어서 처리해야 하거나, 동시성 문제를 해결하는 다른 적절한 방법을 구현해야 할 것 같음.

TypeORM Unique

이렇게 했는데 email이 중복 생성된다.
왜냐면 Unique에 배열을 넣으면, 해당 필드를 각각 유니크한지 체크하는 게 아니라 해당 필드의 조합이 유니크한지 체크하는 것.

import {
  Column,
  CreateDateColumn,
  Entity,
  PrimaryGeneratedColumn,
  Unique,
} from 'typeorm';

@Entity()
@Unique(['email', 'nickname']) //이렇게 하면 안 됨
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  email: string;

  @Column()
  password: string;

  @Column({ nullable: true })
  nickname: string | null;

  @Column({ nullable: true })
  intro: string | null;

  @Column({ nullable: true })
  avatar: string | null;

  @Column({ default: 1 })
  type: number;

  @Column({ nullable: true })
  refreshToken: string | null;

  @CreateDateColumn()
  createdAt: Date;
}

Docker

https://codegear.tistory.com/108

Dockerfile

FROM node:21
RUN mkdir -p /var/app
WORKDIR /var/app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD [ "node", "dist/main.js" ]

.dockerignore

.git
*Dockerfile*
node_modules

도커 파일 생성

docker build . -t nest-docker

상대경로 사용하기

상대 경로를 사용하는 것이 권장되는 이유는 여러 가지가 있습니다. 특히 Docker와 같은 컨테이너 환경에서 빌드하거나 다양한 환경에서 프로젝트를 실행할 때 중요한 역할을 합니다.

환경 독립성: 상대 경로는 프로젝트의 파일 시스템 구조에 대해 환경에 구애받지 않고 일관된 방식으로 참조를 제공합니다. 이는 프로젝트가 다른 기계나 서버, 컨테이너 환경에서 실행될 때 동일한 파일 구조를 유지하기 때문에 중요합니다.

이식성: 상대 경로를 사용하면 프로젝트의 파일을 다른 위치로 이동하거나 다른 시스템으로 복사해도 경로를 수정할 필요가 없습니다. 이는 프로젝트의 이식성을 높여주며, 특히 대규모 팀이나 여러 환경에서 작업할 때 유용합니다.

경로 별칭 문제: TypeScript에서는 경로 별칭을 사용하여 편리하게 임포트할 수 있지만, 이러한 설정은 TypeScript 컴파일러에 의해서만 이해됩니다. JavaScript로 컴파일된 후에는 경로 별칭이 더 이상 작동하지 않을 수 있으며, Docker와 같은 환경에서 빌드할 때 이슈가 발생할 수 있습니다.

명확성과 유지보수: 상대 경로를 사용하면 파일 간의 관계가 명확해지고, 프로젝트의 구조를 이해하기 쉬워집니다. 또한, 프로젝트 구조가 변경될 때 상대 경로를 사용하는 것이 유지보수에 더 편리할 수 있습니다.

import 대소문자

import { Comment } from '../comments/entities/comment.entity';

Commnet.entity일 때 npm 으로 서버를 실행되지만, 도커 빌드에서는 에러가 발생함.

windows와 Mac os는 일반적으로 파일 시스템에서 대소문자를 구분하지 않지만, 도커에서는 대소문자를 구분함.
(리눅스는 구분함)

대소문자 때문에 로컬에서 동작하는 코드가 실서버(리눅스)에서는 동작하지 않을 수도 있음. 대소문자를 정확히 일치시키고, 리눅스 환경에서 테스트가 필요함.

이미지 빌드

컨테이너 실행

docker container run -d -p 3000:3000 nest-docker

-d: detached 모드로 컨테이너를 백그라운드에서 실행
즉, 터미널이 이 컨테이너 프로세스에 바인딩되지 않음.

-p 3000:3000: 호스트의 3000포트를 컨테이너 3000포트에 매핑해라
nest-docker: 이미지 이름

도커로 띄우는 거 성공했다.. 우와..

Gighub actions

https://codegear.tistory.com/84

자동 배포

빌드 실패 문제

github actions 권한을 설정하지 않아서 gh push 실패함.

Error: buildx failed with: ERROR: failed to solve: failed to push ghcr.io/classbinu/junglepedia-ai-be:latest: unexpected status from POST request to https://ghcr.io/v2/classbinu/junglepedia-ai-be/blobs/uploads/: 403 Forbidden

Read and write permissions 권한 주고 해결!

뭔가 들어가긴 함

환경변수 문제

문제1. process.env로 못 읽음

@Injectable()
export class AccessTokenStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
// secretOrKey: process.env.JWT_ACCESS_TOKEN_SECRET,
secretOrKey: 'docker_test_secret',
});
}

문제2. .env도 못 읽음

도커 허브

도커 허브에 올리려면 이미지 태그가 도커 허브 사용자명과 일치해야 함.

docker build . -t classbinu/junglepedia-ai-be

배포

도커와 github actions 자동배포는 거의 다 됐는데 환경변수 문제로 성공 못하고, git clone 방식으로 수동으로 배포함.

빌드 후 nohup으로 백그라운드(&)실행하기

nohup node dist/main.js &

프로세스 종료

ps aux | grep node
kill [PID]

0개의 댓글