typeorm config에서 다음과 같이 synchronize가 true로 설정되어 있다면

import { TypeOrmModuleOptions } from '@nestjs/typeorm';

export const typeORMConfig: TypeOrmModuleOptions = {
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  username: 'postgres',
  password: 'postgres',
  database: 'board-app',
  entities: [__dirname + '/../**/*.entity.{js,ts}'],
  synchronize: true,
};

 

 

entitiy class 정의와  db 테이블 스키마가 일치하지 않은 상태에서 프로그램을 실행시킬 경우에 typeorm이 그 차이점을 인식하고 entitiy class 정의를 기준으로 db 테이블 스키마를 변경하는 방식으로 동기화하게 된다.

 

즉 entitiy 정의에 반영된 컬럼의 추가,삭제,수정이 DB 스키마에 그대로 반영되는 위험성이 있다는 것이다.

 

이러한 문제를 방지하기 위해 synchronize 사용하지 않고 마이그레이션 파일을 만들어 실행시키는 방식으로 DB 스키마에 변경사항을 변영한다. DB 스키마를 체계적으로 관리하고 실제 운영환경에서의 안정성을 위해 수동으로 마이그레이션하는 방식을 사용한다.

'개발 > Nest.js' 카테고리의 다른 글

[Nest.js] TypeORM 사용시 EntityMetadataNotFoundError 해결  (0) 2024.07.13

TypeORM에서 다음과 같이 Repository를 구성하는 경우 EntityMetadataNotFoundError가 발생한다.

 

 Board 도메인에서 createBoard API를 구성하는 것을 예제로하여 살펴보자

 

module.ts

@Module({
  imports: [TypeOrmModule.forFeature([BoardRepository])],
  controllers: [BoardsController],
  providers: [BoardsService],
})

 

controller.ts

@Controller('boards')
export class BoardsController {
  constructor(private boardsService: BoardsService) {}

  @Post('/')
  @UsePipes(ValidationPipe)
  createBoard(@Body() createBoardDTO: CreateBoardDTO): Promise<Board> {
    return this.boardsService.createBoard(createBoardDTO);
  }
}

 

service.ts

@Injectable()
export class BoardsService {
  constructor(
    @InjectRepository(BoardRepository)
    private boardRepository: BoardRepository,
  ) {}

  createBoard(createBoardDTO: CreateBoardDTO): Promise<Board> {
    return this.boardRepository.createBoard(createBoardDTO);
  }
}

 

Repository.ts

@EntityRepository(Board)
export class BoardRepository extends Repository<Board> {
  async createBoard(createBoardDTO: CreateBoardDTO): Promise<Board> {
    const { title, description } = createBoardDTO;

    const board = this.create({
      title,
      description,
      status: BoardStatus.PUBLIC,
    });

    await this.save(board);

    return board;
  }
}

 

createBoard API를 요청하는 경우 다음과 같은 에러가 발생한다

 

ERROR [ExceptionsHandler] No metadata for "BoardRepository" was found.
EntityMetadataNotFoundError: No metadata for "BoardRepository" was found.

 

이는 @EntityRepository가 지원 중단되어 nestjs가 Repository 클래스를 인식하지 못하기 때문에 발생하는 에러이다.

 

typeORM 사용시 custom repository를 만들기 위해서는 Repository와 Controller 클래스를 다음과 같이 수정해야 한다.

 

Controller

(provider에 Repository 추가)

@Module({
  imports: [TypeOrmModule.forFeature([BoardRepository])],
  controllers: [BoardsController],
  providers: [BoardsService, BoardRepository],
})

 

Repository

(Repository 클래스를 Injectable로 변환 후 모듈에 주입하고, constructor를 통해 엔티티 관계를 직접 설정해준다.)

@Injectable()
export class BoardRepository extends Repository<Board> {
  constructor(dataSource: DataSource) {
    super(Board, dataSource.createEntityManager());
  }

  async createBoard(createBoardDTO: CreateBoardDTO): Promise<Board> {
    const { title, description } = createBoardDTO;

    const board = this.create({
      title,
      description,
      status: BoardStatus.PUBLIC,
    });

    await this.save(board);

    return board;
  }
}

 

I. 프로젝트 소개

대기환경 모니터링 시스템은 교내 캡스톤 과정의 일환으로 약 1년간 진행한 8인 프로젝트이다. 교내 가로등에 설치된 센서노드를 통해 데이터를 측정 및 저장하고, 사용자는 캠퍼스 대기환경정보를 열람할 수 있는 서비스이다.

 

첫번째 학기(23-2)에는 환경조사, 문제정의, 토이프로젝트 등을 진행하였고 두번째 학기(24-1)에는 대기환경 모니터링 시스템 구축을 본격적으로 진행하였다.

 

 

II. 캡스톤 매칭과정

우리 학교의 캡스톤 과목 진행방식은 교수님 별 프로젝트 주제들이 공개되고 학생이 흥미있는 주제를 신청하여 팀 빌딩이 이루어지는 방식이다. 일반적으로 한 분의 지도교수님에 2~4인 팀과 특정한 주제가 있기 마련인데 내가 캡스톤을 신청하는 학기에는 처음으로 포항시 환경문제를 중심으로 하는 8인팀의 자유연구 주제가 생겼다. 정해진 연구주제에 비해 진취적으로 문제를 탐색하고 해결할 수 있다는 점이 매력적으로 다가왔다. 지도교수님도 다섯 분이 계셨고, 하드웨어부터 소프트웨어까지 개발하는 팀이었기에 다양한 경험을 통해 성장할 수 있을 것이라는 기대가 생겼다. 고민없이 해당 주제를 신청하였다.

 

 

III. 환경문제 조사

1. 조사과정

가장 처음에 한 일은 포항시 북구의 환경문제를 조사하는 것이었다. 우리 팀은 온라인 기사 서치로 문제를 찾아나서기 시작했고 더욱 실질적인 정보를 얻고자 환경운동가와 기자분들을 찾아가 인터뷰를 진행하기도 하였다. 뙤약볕에 근처 마을답사를 진행하며 하며 주민분들과 여러차례 이야기를 나누기도 하였다.

 

가장 어려웠던 것은 점은 특정한 이슈에 대해서 시민, 환경운동가, 기업 별로 서로 다른 입장을 취하고 있어 객관적인 문제 발견이 힘들었던 것이다. 기사나 환경운동가, 일부시민들은 문제를 제기하고 이를 고발하는 다큐멘터리 영상도 찾아볼 수 있었다. 그러나 기업, 공무원, 일부시민들은 그런 문제들이 전혀 없다고 주장했다. 게다가 각 측의 주장 근거로 제시된 자료의 수치도 서로 달라서 객관적인 기준을 세우기 어려웠다.

 

이처럼 환경이슈에 관한 일관된 문제 혹은 주장을 찾을 수 없었고 그저 입장에 따라 현상을 다르게 보는 것 같았다. 이러한 과정을 거치면서 도저히 무엇이 문제이고 무엇이 진실인지를 모르겠다는 생각을 수없이 했다. 그리고 대부분의 사람들은 환경에 대해 큰 관심이 없다는 것도 알게 되었다. 개인적으로는 기술은 수단이 되어야 하고 그 목적은 세상을 이롭게 하고 이웃을 돕는 것이 되어야 한다고 생각한다. 프로젝트의 첫 단추를 리서치로 시작했던 것은 이러한 맥락으로 문제를 정확히 포착하기 위함이었지만 그러기 쉽지 않았던 것 같다.

 

2. 문제정의

그러가 교수님의 권유로 학교 근처 산업단지 조성사업에 관한 조사를 시작했다. 환경보전방안서, 사후환경영향조사 등 수천페이지 분량의 보고서들을 읽어보며 산단조성 과정과 현황에 대해 공부했다. 보고서 상의 미흡한 점들을 발견할 수 있었고 이를 기반으로 문제를 정의하기 시작했다. 우리 팀이 주목한 것은 사후환경 조사과정에서 진행되는 대기환경 모니터링의 주기,위치,시기와 관련하여 미습한 부분이 존재한다는 것이었다. 모니터링은 분기별 1회(연간 4회) 진행되고 있었고 1회 측정시 2~3일 측정하였다. 또한 전원공급이 가능한 일부 장소에서만 진행되고 있었다. 측정데이터 중 일부는 비가 오는 날 혹은 비가 온 이후 측정되기도 하여서 평소 대기상황과 차이가 클 것으로 예상되었다.

 

이러한 문제의 해결방안으로 독자적인 모니터링 시스템을 구축을 구축하기로 하였다. 기존 모니터링 방식을 보완하고 독립적인 대기환경 데이터를 확보하여 산단으로 인한 대기환경 영향과 그 추이를 측정할 수 있도록 하는 것이다. 더불어 이러한 시도 자체가 기업들에게 경각심들 심어주거나 시민들에게 환경에 대한 관심을 갖게 할 수 있을 것이라는 기대도 할 수 있었다.

 

 

IV. 프로젝트 진행

하드웨어부터 소프트웨어까지 모두 우리 팀이 제작해야 했기에 나름 대형 프로젝트였던 것 같다. HW 부분에서는 재료 주문부터 시작해서 센서노드 제작, 방수 테스트, 설치, 유지보수 등을 진행하고 SW 부분에서는 센서 네트워크 게이트웨이 구축, 웹 디자인, 관리자/클라이언트 웹 제작 등을 진행하였다. 나는 서버 개발을 중점으로 맡았지만 로라 모듈 통신 테스트 및 아두이노 센서노드 로직 작성 등을 함께하며 하드웨어에 관해서도 경험을 쌓을 수 있었다. 프로젝트 초기에 로라 모듈 linear 통신 테스트, 아두이노 시리얼통신 관련 트러블 슈팅을 하면서 ‘오 됐다!’하면서 기뻐했던 것이 기억에 남는다.

 

주로 담당한 작업들을 나열해보자면 다음과 같다.

- 시스템 아키텍처 설계

- Firebaes Collection 구조 설계

관리자 웹 API 서버 개발

클라이언트 웹 API 서버 개발

Raspberry PI Gateway 개발

통계 데이터 생성을 위한 cron-job 루틴 구현

Arduino 센서노드 로직 일부 구현

클라이언트 페이지 UI 일부 구현

센서노드 제작/테스트/설치/유지보수 (센서노드 관련해서는 노가다성 작업을 많이 했던 것 같다. 컨트롤박스 구멍뚫기, 통신 테스트, 실리콘바르기, 가로등에 고무패드 붙이기, 센서고정하기 등)

 

 

V. 조직관리

캡스톤 마치고 나니 소프트스킬에 대해서도 많이 고민하고 배운 것 같다.

 

1. 효율적인 프로젝트를 위한 조직관리 방법 제안

첫 학기에는 팀원 8명 모두 환경조사 과정에 참여했고 함께 문제를 탐색해 나갔다. 각자가 조사하는 분야과 내용이 달랐고 팀을 나누어 진행하기도 하였다. 그러다보니 정보가 산발적으로 흩어져 팀원 별로 인지하고 있는 내용들이 달랐다. 이때 8명의 팀원들이 마음을 모아 함께 소통하면서 프로젝트를 진행하는 것이 생각보다 어려운 일이라는 것을 많이 느꼈던 것 같다. 조사내용을 공유하고 서로 싱크를 맞추기 위해 notion 문서화와 진행상황을 공유하는 시간을 따로 갖는 것을 제안했다. 또한 회의 시간에도 이야기를 하다보면 체계가 안잡히고 언제 끝날지 모르는 회의가 되는 경우가 종종 있었는데, 안건을 명확히 하고 회의 시간을 정하는 방식으로 문제를 해결할 수 있었다. 우리 팀만의 체계가 조금씩 갖춰지면서 팀원 간 의사소통이 개선되는 것을 느낄 수 있었다.

 

2. 팀원 간 열정/태도의 차이

프로젝트를 대하는 열정의 정도가 모두 달랐던 점이 힘들었던 것 같다. 누군가는 적극적으로 시도하고 문제가 발생하면 해결하려는 태도를 가진 반면 누군가는 일을 주어지지 않는 이상 스스로 나서서 하지 않는 소극적인 태도를 갖고 있었다. 몇 차례 이런 태도의 차이로 인해 발생하는 서운함을 터놓았지만 상황이 달라지는 않았다. 프로젝트를 대하는 온도나 마인드가 사람마다 다른 것이니 내가 어떻게 할 수 있나 하는 생각이 들었다. 사람 마음을 움직이는게 가장 어려운 일이니 말이다. 그래서 다른 사람의 열정은 신경쓰기 보단 열정있는 사람들과 먼저 열심히 하다보면 서로 으싸으싸할 수 있지 않을까하는 생각으로 최선을 다하려고 했다. 하지만 여전한 일부 팀원의 모습에 점점 일부 팀원에 대한 기대를 잃었던 것 같다. 돌아보면 너무 빨리 일부 팀원에 대한 기대를 포기한 것이 아닐까 하는 생각도 든다. 8명이 함께 만들어가는 프로젝트인데 하나되지 못했던 것에 모습에 아쉬움이 남는다. 그럼에도 불구하고 이러한 문제의식과 감정들을 터놓고 대화하면서 그러한 과정이 필요하고 또 의미있는 것이라는 것을 알게 되었다. 솔직한 피드백과 소통이 협업에서 가장 중요한 요소 중 하나라는 것을 1년 간의 시간을 통해 알게 된 것 같다. 앞으로도 성공적인 협업을 위한 의사소통이 무엇일지 고민하며 배워가는 시간을 갖고 싶다.

I. 문제의 시작, 불편함

학생에게 성적은 꽤나 중요하다. 우리 학교는 기말고사 후 2주간의 성적 입력기간이 존재한다. 2주의 기간동안 성적 확인을 위해 교내 사이트를 들락날락하는 것이 참 귀찮았다. 사이트에 접속하고, 로그인을 하고, 학사정보의 성적 탭을 누르고, 또 현 학기 성적/석차 조회 탭을 누른 뒤에 스크롤을 내려야만 성적을 확인 할 수 있다. 무려 4번의 클릭과 스크롤이 요구되었다. 게다가 성적이 아직 미입력이라면 다시 또 이런 귀찮은 과정을 거쳐야 했다.

 

모든 학우를 위해 성적알림서비스를 만들어야겠다고 생각했을 때(23-2학기), 성적 확인 과정의 불편함이 나 혼자만 느끼는 것은 아닐까? 하는 의문이 들었다. 나만의 문제라면 굳이 서비스를 만들 필요가 없기 때문이다. 그래서 문제를 검증해보았다. 주변 학우들을 만날 때마다 내가 느낀 불편함과 서비스 아이디어를 나눴다. 수십명의 학우들과 이야기를 나눈 결과 대부분의 학우들이 겪는 공통의 문제라는 것을 확인할 수 있었다. 그렇게 프로젝트는 시작되었다. 나의 문제가 아닌 우리의 문제로부터 모두가 공감하는 포인트를 가지고 출발했다는 점이 진행 과정 중에 발생한 어려움을 극복하고 성장할 수 있었던 원동력이었다.

 

II. 문제 해결 과정

초기 접근 (2023-1 학기)

처음 문제의식을 마주한 23-1학기에는 개인적인 불편함 해소를 위해 나를 대상으로 하는 프로그램을 만들었다. 단순히 카카오톡으로 알려주면 좋지 않을까 하는 생각에 카카오톡 api를 연동하여 나에게 보내기로 알림을 주도록 했다. 그래서 따로 배포하지 않고 로컬에 백그라운드로 실행하여 성적입력 기간동안 동작하도록 해두었다. 실제로 알림을 받으니 정말 기분이 좋았다!

 

정식서비스 개발 및 배포/운영 (2023-2학기)

다음 학기인 23-2학기에는 정식서비스를 만들어야겠다고 다짐했다. 나만의 문제의식이 아니라는 것을 검증한 후에 바로 개발에 착수했다.

  1. 태도
    기말고사 기간과 일부 겹쳐 개발을 시작했기에 시간적 압박이 있었다. 하지만 서비스가 배포되어서 학우들에게 불편함이 해소되는 기분 좋은 경험을 선물해주는 장면을 계속해서 상상했다. 그리고 주변 학우들에게 이것을 이뤄낼 것이라는 것을 말했다. 누군가에게 목표를 이야기하고 그것을 지속적으로 상상하는 것의 힘을 알았기 때문이다. 학우들이 겪는 이 문제를 내가 해결해야겠다는 나름의 사명감도 갖게 되었다. 그래서 자연스럽게 서비스를 사용하는 유저에게 최고의 UX를 선물하는 것에 가치를 두고 임했다.

  2. 필요기능 정의
    • 프론트엔드
      • 입력받는 정보는 아이디, 비밀번호, 문자알림을 받을 전화번호이다.
      • 잘못된 계정이 입력된 경우 성적알림이 가지 않는 최악의 상황이 발생한다. 그래서 유저가 약간의 기다림을 감수하면서 정확한 알림발송을 위해 계정입력 과정에서 유효성 여부를 확인하도록 했다.
      • 추가적으로 전화번호와 계정의 중복 여부도 체크하였다.
    • 백엔드
      • 프론트엔드에서 요구되는 유효성 체크 및 중복 체크, 알림예약 등의 API를 구현한다.
      • DB 계정정보에 대해 일정시간마다 성적 업데이트 여부를 확인하여 성적이 업데이트 된 경우 문자를 발송하는 루틴을 구현한다.

  3. 기술 스택 선정
    • 웹으로? 앱으로?
      가장 첫 고민은 개발을 웹으로 할지 앱으로 할지에 대한 고민이었다. 결정은 꽤 쉽게 내렸다. 성적알림 받으려고 앱을 설치 하는 것이 유저에게 오히려 귀찮음을 야기할 것이라고 생각했다. 나였어도 굳이 설치하기 싫었을 것 같다. 그리고 아이디, 비밀번호, 전화번호 정도만 입력하면 되었기 때문에 웹으로 만드는게 좋겠다고 판단했다. 링크하나만 있으면 되기 때문이다.

    • 웹 기술 스택 선정
      단 기간에 서비스를 개발해야했기에 나에게 가장 익숙한 스택(react, node, firebase, ec2)을 사용했다. 프론트의 경우 1~2페이지만 개발하면 되기에 굳이 react를 사용할 이유는 없었지만 react 사용 경험을 쌓기 위해 사용하기로 했다.

    • 어떤 방식으로 알림을 보내야 할까?
      카톡, 이메일, 문자 등 다양한 알림수단이 존재했다. 알림발송 시 유저가 알림을 바로 확인할 수 있는 것이 가장 중요하다고 보았다. 이메일을 잘 확인하지 않는 학우들이 있으므로 이메일은 제외했다. 공지의 성격이 있기 때문에 카톡보다는 문자가 상대적으로 적합하다고 판단했다. 카톡을 잘 확인하지 않거나 다른 카톡에 의해 알림이 묻히는 등의 경우가 있을 수 있다고 생각했다. 문자 API로는 네이버 클라우드 플랫폼의 Simple & Easy Notification Service를 사용하려 했으나 당시에 일시적으로 지원이 중단된 상태였다. 다른 api를 찾아보았는데 종류가 꽤 다양했다. 가격, UI의 직관성, 사용의 편의성 등을 비교하여 solapi를 사용하기로 했다.

  4. 서비스 구조
서비스 아키텍처

 

5. 서비스 홍보
에브리타임, 실명카톡방, 지인 카톡방 등의 채널에 공지를 작성하여 올렸다. 공지 글도 어떻게 하면 학우들이 서비스에 대해 공감하고 명확히 알 수 있을까?를 생각하며 작성하려 노력했다.

6. 운영 과정

  • 서버가 터졌다
    홍보 후 거의 바로 하나 둘씩 유저가 들어오는 모습을 모니터링하며 신기하고 뿌듯했다. 서비스가 인정받는 느낌도 들어 기분 좋았다. 하지만 10분도 채 안되어서 트래픽이 조금 몰리니 cpu util이 증가해서 서버가 죽었다. 복잡한 작업을 하는 것이 아니라 무엇이 문제인지 파악하기가 힘들었다. 일단은 서비스가 정상 동작하는 것이 중요했기에 바로 인스턴스 수준을 업그레이드 했다. 인스턴스 수준 올리고 재부팅하는 5분이 5년처럼 느껴졌다. 아마도 free tier를 사용했어서 조금만 트레픽이 발생해도 낮은 하드웨어 스팩으로 인해 문제가 되는 것 같았다.

    명확히 원인을 파악하지 못하고 cpu util도 종종 올라가서 새벽 2~3시까지 모니터링하고 원인을 찾으려 했다. 유저는 계속 들어오고 에브리타임의 좋아요 수는 늘어났지만 내 근심과 걱정도 같이 늘었다. cpu util 증가로 인해 서버가 3번 정도 멈추었고 그 때마다 인스턴스를 재시작하는 방식으로 대응했다. 2분여만에 복구되었지만 그 사이에 서비스가 동작하지 않는 모습을 마주했을 유저를 생각하니 뭔가 마음이 아팠다.

    로직상의 문제가 있을 수 있다는 생각에 코드를 살펴보았지만 복잡한 로직은 없었기에 특별히 문제되지는 않을 것 같았다. 그래서 ec2 하드웨어 스팩상 램이 부족하거나 cpu성능이 낮은 문제가 클것이라고 생각했다. 부하를 조금이라도 덜기 위해 react와 node를 별도의 ec2로 분리하여 재배포하였고 CPU util이 다소 안정화된 것을 확인할 수 있었다.
    (후에 swap space를 늘려서 성능을 최적화시키는 방법을 확인할 수 있었는데 free tier 스팩이 아쉽긴 한가보다.)

  • 배포가 다가 아니다, 유저들의 문의 및 해야할 일이 생각보다 꽤 많았다!
    맞닥뜨린 문의사항과 에러를 정리해보았다.
    • 유저가 백명 이상이 되었을 때 solapi 일일 제한을 풀어야 했다. 당장 50개 이상의 성적 업데이트가 발생하지는 않을 거라 생각했지만 언젠가 해야할 일이었다. solapi의 경우 sms api의 일일 50개 제한이 걸려있었고 사용내역이 있어야 제한 해제 문의가 가능한 구조였다.
    • 등록한 계정정보를 삭제해달라는 유저가 계셨다. 개인정보 이슈로 계정삭제를 문의주셨던 것이다. 계정 삭제 기능은 생각도 안했었는데 급히 만들어 삭제한 후 답변을 드렸다.
    • 전화번호를 잘못 입력하신 유저가 계셔서 변경요청이 들어왔다. firebase 특성상 문서ID를 하나씩 눌러야만 정보를 확인할 수 있었기에 기능을 따로 만들어 전화번호 수정 후 답변을 드렸다.
    • 서비스 원리를 여쭤보시는 분도 계셨다.
    • 유저의 각종 요청/문의 응대하랴, 서버 모니터링하랴 약간 정신이 없었다. 늦은 밤이여서 피곤했기에 멘탈을 잘 잡자고 스스로를 독려했다.
    • 예상치 못한 오류도 발생했다.
      DB에 저장된 계정정보 중 유효하지 않은 계정이 있어서 업데이트 확인+알림전송 루틴 실행 중 타임아웃이 발생했다. 에초에 유효한 계정만 등록이 되도록 로직을 구현했는데 어떻게 유효하지 않는 계정이 db에 저장되어 있는 것인지 의문이 있었다. 나중에 생각해보니 사용자가 계정을 등록한 후에 비밀번호 변경한 것이라고 생각이 되었다.

      앞으로 로직을 구현할 때는 유저의 사후적 계정정보 변경에 대응할 수 있는 등 프로그램이 데이터에 의존하지 않도록 해야겠다고 생각했다. 유저가 생각 밖의 행동을 할 수도 있다는 것을 인지하고 예외처리를 꼼꼼하게 해야겠다는 생각을 했다.

7. 학교 정보화개발팀 연락과 서비스 중단

다음날 오전에 학교 정보개발팀에서 유선연락이 왔다. 학교 규정에 위반되는 부분이 있어 서비스 중단을 요청을 받았다. 그렇게 서비스는 배포 14시간만에 중단되었다. 어떤 부분이 위반되는지 궁금했고 문의를 보냈다. 그리고 정보개발팀에서 친절하고 구체적인 답변을 받을 수 있었다. 크게보자면 1) 원칙적으로 크롤링 금지, 2) 개인정보 동의 절차 미비, 3) 보안문제(ssl 적용안됨), 4) 정보유출 시 책임소재 미비 였다.

 

개인정보를 취급하는 터라 관련 검토가 필요했는데 그러한 검토과정 없이 서비스를 배포했었다. 홈페이지와 홍보과정에서 개인정보를 성적알림 외 용도로 사용하지 않겠다고 밝혔고, 개인정보가 오용되거나 노출하는 것이 꺼림칙한 사람은 서비스를 에초에 사용하지 않을 것이라고 생각했기 때문이었다. 그럼에도 개인정보를 사용한다는 것을 심도있게 고려했어야 했는데 그러지 못하고 지나쳤던 것을 인지하고 반성하게 되었다.

 

그럼에도 보안을 강화하고 개인정보처리동의 절차를 삽입한 후에 학교 측의 동의가 있다면 서비스가다시 재개될 수 있을 거라고 생각했다. 한국법을 복수전공하고 있는터라 궁금증이 생겨 관련판례들과 정보를 찾아보았다. 사이트 계정이 개인정보에 해당하면 학생이 동의하는 경우 문제 되지않았지만 동시에 학교의 정보자산이라는 점에서 학교측과의 동의가 없다면 여전히 문제가 있었다. 하지만 책임소재와 관련된 부분을 제외한다면 내 생각으로는 다면 충분히 학교측에서 용인가능한 서비스라고 생각했다. 정보화개발팀에 정식으로 찾아가 보완사항 및 운영계획을 공유드리며 양해를 구하는 시간을 가져야겠다고 생각했다. 다만 당시 시간적, 정신적 여유가 없어 이러한 보완사항들은 다음학기에 진행하기로 했다.

 

홍보했던 채널들에 다시금 중단 및 개인정보 정보폐기를 내용으로하는 공지를 올리는게 마음이 아팠다.

 

8. 유저의 반응이라는 큰 위로. 200명의 유저.
14시간의 짧은 러닝타임이었지만 200여명의 유저가 유입되었고 커뮤니티의 반응을 통해 내가 느낀 문제의식에 대해서 학우들도 함께 공감하고 있다는 것을 다시금 확인할 수 있었다. 그리고 무엇보다 지인과 다른 학우들의 응원과 격려가 정말 감사했다.

 

어쩌면 성적알림주는 프로그램 만드는 것이 뭐 대단한 일인가 싶을 수 있다. 그렇지만 나의 개인적인 불편함으로부터 시작하여 학우들의 문제를 해결하고자 노력하고 유저의 반응을 확인하고 위로와 격려를 받으며 서비스에 대한 애정이 커졌다. 그만큼 서비스가 중단되었을 때의 아쉬움은 컸다. 친한 친구 한명이 지나가면서 했던 잊을 수 없는 한마디가 있다. “그날 밤에 알림 문자 2개나 와서 기분 좋았는데!” 그 한마디가 내가 학우들의 삶에 작게나마 기여했다고 인정받는 듯해서 큰 뿌듯함과 위로로 다가왔다.

서비스 중단 후 익명커뮤니티 반응
문자 알림 전송 내역 일부


후속 시도 (2024-1학기)

다음 3가지를 중점으로 보완내용을 공유드리면서 정보개발팀측에 동의를 구하는 메일을 드렸다.

  1. 개인정보동의절차 삽입
  2. ssl 보안설정
  3. 성적의 등록 여부만 저장

하지만 지난 학기 서비스 중단요청과 동일한 이유로 동의를 받지는 못하였다. 역시 교내 사이트 계정정보가 학교 정보자산이라는 점과 외부로 유출되었을 때의 책임 문제가 가장 큰 이슈였다. 정보개발팀 선생님께서 교무팀 성적담당 선생님께 건의를 드려보면 어떨지 조언을 해주셨다. 아쉬운 마음을 뒤로한 채 교내 성적담당 선생님께 성적알림 기능의 도입을 정식으로 요청드리는 것으로 나와 학우들의 귀찮음을 극복하고자 했던 시간은 매듭을 짓게 되었다.

 

III. 소회

짧은 시간이었지만 내가 속한 학생 공동체의 문제를 풀기 위해 고민하고 몰입하는 경험을 할 수 있어서 감사했다. 문제해결을 목표로 서비스를 개발하는 것의 즐거움을 많이 느낀 것 같다. 더불어 성적확인 과정의 불편함을 제거한다는 목표가 명확하니 개발 및 운영과정에서 에러를 만나고 서버가 터져도 열정을 다하고 긍정적인 태도로 임할 수 있었다. 그리고 서비스가 사용되는 모습을 지속적으로 상상하면서 스스로 대뇌이고 생각하는 것이 목표를 현실화하는 원동력이라는 것도 깨달았다. 이것이 론 다번의 The Secret이라는 책의 핵심내용과 동일한데, 머리로만 알고 있던 것을 체득할 수 있었던 좋은 시간이 된 것 같다.

 

개인적으로 나는 개발자가 개발만하는 사람이 아니라 문제를 해결하는 사람이라고 생각한다. 그래서 나에게 코딩은 하나의 문제해결의 도구에 불과하다. 내가 해결하고자 하는 문제가 코딩을 요구하지 않는다면 다른 방법을 통해 문제를 해결해야 할 것이다. 앞으로의 삶도 이와 같이 문제해결 과정의 연속이라면 의미있는 삶이 되지 않을까 생각한다.

0. 팀매칭과 기획

- A리더를 택한 이유

  내가 A리더님을 택한 이유는 삘이 꽂혔기 때문이다. 그러기도 했고 이번에 ICT창업학부 캠프에 가서 T-lab친구들의 열정을 보았기 때문에 T-lab장이라는 A님이 진국이는 생각이 들었다. 그리고 팀매칭 시간에 간략히 회의를 했는데 그때 헤커톤을 위해 많은 조사와 노력을 들이고 있다는 것을 알게되면서 헤커톤에 진심이구나하는 생각이 들며 열정 넘치는 리더라고 생각이 되었다. 그러면서 자연스레 나도 이번 헤커톤이 단순 경험이 아니라 진심을 다해 준비하면서 1등을 향해 달리고 싶다는 동기를 갖게 되었다. 난 A님이 사람의 마음을 움직일 줄 아는 참 리더라고 생각했다. 헤커톤이 끝난 지금도 A님은 나에게 사람을 움직이며 원할한 의사소통으로 팀을 조율할 줄 아는 좋은 리더로 남아있다.

- 아이디어 선정

  우리팀은 아이디어 선정에서 꽤 애를 먹었다. 처음 A님의 아이디어로 cctv 정보제공을 통해 1인가구의 범죄율을 낮추는 식의 솔루션을 설정하는 방향으로 진행되었지만 이미 좋은 서비스들이 있었다. 그 후 재윤님의 공유차량 사고로 부터 파생된 법률정보제공 서비스로 방향을 돌렸지만 많은 이야기 끝에 정보제공 서비스를 하기에는 이미 많은 서비스들이 존재하였기에 차별화를 하기 쉽지 않다는 결론에 이르렀다. 아이디어에 늪에 빠졌었다.

 

  리프래시를 하기 위해 A님과 나는 밖으로 잠시 나와 앉아 이야기를 했다. 그 때 난 연인간의 소통이나 가족간의 소통을 도울수 있는 서비스를 제안했다. 나의 전 연애도 소통문제로 인해 관계가 안좋아졌고 우리 가족간에도 서로 잘 알지 못하고 있다는 문제인식을 하고 있었기 때문이다. 무거운 마음으로 다시 팀원들에게 아이디어를 소통관련으로 다시한번 갈아엎자는 제안을 했고 썸원이라는 매일의 질문을 통해 연인이 서로를 잘 알아갈 수 있도록 돕는 서비스를 밴치마킹하여 아이디어를 구체화시켜 나갔다. 인간이라면 누구든지 가족과 더 화목해지고 싶고 더 알아가고 싶은 마음이 있었기에 우리팀 구성원 모두는 우리 서비스가 해결하고자하는 포인트를 모두 공감할 수 있었고 이것이 우리가 서비스를 만들어 가는 헤커톤 기간에 있어서 강한 동기부여가 될 수 있었던것 같다.

- 이것이 기획이다.

  평소 창업에 관심이 있던 나이기에 아이디어를 구체화하고 서비스로 구현되어 가는 과정이 굉장히 흥미로웠다. 특히 멘토링시간에 현업에서 일하시는 대표님의 피드백에 아직도 뇌리에 남아있다. 창업자가 서비스가 해결하고자 하는 문제에 진심이여야 한다는 것이었다. 자기자신이 개인적으로 진심이고 공감할 때 그 진심이 묻어가게 되고 그것이 문제를 해결하고자하는 동기가 되어 사업 아이템이 나와야 한다는 것이다. 이것이 자연스러운 과정으로 흘러가지 않고 순서가 뒤바뀌게 되면 다른 사람들은 여기부터 얘내가 거짓말이구나 알아차린다고 했다. 사실 그러한 진심이 없으면 좋은 서비스가 되기 힘들것 같다. 개인적 경험과 진심으로 부터 나온 문제를 해결하고 동기가 해결방안을 모색하고 서비스를 구체화시킬때 가장 이상적인 기획이라고 생각이 들었다. 가장 개인적인 것이 가장 창의적인 것이라는 말처럼 개인의 진심과 시장성이 맞물릴때 좋은 서비스가 되는 것 같다. 문제에 대한 진심과 해결방안이 우선이어야 하며 돈버는 것은 그 다음에 생각해도 늦지 않는다는 조언이 뭔가 감동이었다.

1. 의사소통

- 기획자, 디자이너와 소통하는 방법

기획자와 소통하는 방법, 디자이너와 소통하는 방법 대해 조금 배운것 같다. 디자인 시안을 보며 궁금한 것과 시안을 계속 확인차 물어보며 디자이너의 이상을 계속적으로 싱크하려고 노력하는 것이 더 좋은 협업방식이라고 느껴졌다. 모두가 공감하는 문제로 부터 구체화된 아이디어가 소통을 원할하게 하도록 돕는 요인이기도 했던 것 같다.

- 감정적 갈등과 리더의 자질

  소통할때 A리더님이 감정을 삭히고 차분하게 말하는 장면이 머리에서 잊혀지지가 앉는다. 개발자B님이 서비스 흐름에 대한 오해를 하고 있었고, 재윤님은 충분이 설명했다고 생각을 했다. 그래서 백엔드 만드는 B님은 오해한 탓에 합의가 된 사항이냐며 왜 처음부터 말하지 않았나고 짜증을 냈다. A님은 이런 상황에서 B님에게 정말 딱 필요한 말만 딱딱딱 이성적으로 말했다.

 

첫째, 나는 충분하게 설명했다고 생각했는데 그러지 않은 것 같다. 미안하다.

둘째, 이미 지난일이니 오해풀고 이대로 진행하자. 지금에서 짜증내봤자 변하는 거 없다는 거 B님도 잘 알지 않냐

셋째, 오해가 발생할 수 있는데 이걸 바로 잘 말해줘서 오히려 고맙다라고 전했다.

 

  B님의 짜증스러운 말투에 순간적으로 분위기가 얼었지만 리더의 자질로 분위기가 오히려 반전되었다. 그 후 밥먹으면서 잘 풀린 것에 대해 오히려 더욱 화기애애해졌다. A리더님은 정말 성숙한 사람이라고 느껴졌고 이게 대표의 자질이지 라는 생각이 강하게 들었다. 나였다면 기분안좋은 티를 팍팍내고 이 후 프로젝트 진행과정에서 의사소통에 차질이 생겼을 것 같다. 감정적인 것보다 이성적으로 생각하고 말하여 상황을 해결하고자 하는게 완전 리스펙이였다.

2. 개발에 관하여

- 실력에 대한 자각

  아이디어가 구체화 된 후 개발과정에서, 몰입해서 막 코드를 짜야하는데 실력이 부족해서 정체되는 듯한 느낌이 들었다. 허무하게 시간을 보내는 듯함을 느끼는 구간이 있었다. 군에서 플러터 개발을 했던 적이 있지만 2년전이었고 해커톤 팀매칭이 된 후 벼락치기 한 탓에 자유롭게 코딩을 할 수 있는 느낌까지는 아니였다. 그래서 그런지 종종막히는 구석이 있고 내 생각대로 모률화시키고 구조짜는게 생각보다 시간이 걸렸다.

- 클린코드

  B 님의 코드보면서 클린코드에 대해 자각하게 되었고 하드코딩을 지양하게 되었다. 처음에 B님이 자기가 짜놓은 탬플릿 가지고 작업을 하라고 해서 왜 귀찮게 일을 두번하게 만드는 거지 하는 마음에 살짝 기분이 좋지는 않았다. 하지만 개발을 조금 하면서 outing과 이미지 url, style 등에 대해 모두 하드코딩을 제하고 나니 오히려 읽기 편하고 생산성도 올라갔다. 수정하기도 매우 쉬웠다. 개발자 향로님이 코드잘짜시는 분들 보면 정말로 잘 쓴 글을 보는 듯한 느낌을 받는다고 했는데 이번에 B님의 코드를 보면서 그런생각이 들었다. 백엔드코드짜는거 읽어보니 정말로 글을 읽듯이 읽히는 느낌을 처음을 받아봤다.

- 3인3색 개발자들

  개발자 C님은 상대적으로 플러터 경험 개발경험이 적기도 했고 교환학생 기간 중에 있느나 준비할 환경이 마땅치 않았다. 중간에 개발환경 오류도 나서 첫째날 밤에 고생도 많이 했다. 하지만 C님은 거기서 자신이 할 수 있는 것을 찾아 해주었다. 협업경험과 깃헙에 대한 지식으로 B님과 나를 도와주었고 깃헙리드미를 작성하고 일정을 관리해주는 등 중요한 기여를 해주었다. 군 헤커톤 때로 일정조율이나 깃험 이슈 사용 프로젝트 flow등을 관리해주고 관리자역할을 해주어서 너무 고마웠었는데, 이번에도 그러한 역할을 잘 해주었다. 개발자 3명이 있었지만 C님은 프로젝트관리와 전반적인 계획에 강점이있었고, 나는 개발뿐 아니라 전체적인 기획과 디자인을 아울러보는 시각을 갖고 있었고 B님은 정말 물흐르듯 좋은 코드, 클린코드를 짜고 그러한 방향성을 제시하며 개발을 리드해 주었다. 3인 3색의 개발자와 협업하면서 B와 C의 강점을 보며 많이 배운것 같다.

3. 감사

  이렇게 헤커톤을 회고하고 나니 배운것이 정말 많은 귀하고 값진 경험이었다는 생각에 너무너무 감사하다. 나에게 귀한 자산이 된 것 같다. 아무것도 없었던 환경에서 해커톤을 개최에 도전한 20학번 김00 학우와 아지트팀에게 감사하다. 당신의 도전이 우리에게 선한 영향력으로 돌아왔습니다.

 

1. 경우의 수를 구하는 방법은 모든 의상분류의 의상 수 +1한 값을 곱한 값에 1(알몸 상태)을 뺀값이다.

2. map을 사용하여 item_category의 중복 없이 입력받는다.

3. map["item_category"]++;  와 같이 입력받을 경우 해당 의상분류마다 카운팅이 가능하다.

 

케이스별 답을 출력할때 \n을 해주지 않으면 틀렸습니다가 나온다.

 

#include <iostream>
#include <algorithm>
#include <map>
#include <string>

using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;

    while(t--){
        map<string, int> m;
        int n;
        int result = 1;

        cin >> n;
        for (int i = 0; i < n; i++){
            string a, b;
            cin >> a >> b;
            m[b]++;
        }

        for (auto i : m){
            result *= (i.second + 1);
        }

        cout << result - 1 << "\n";
    }
}

 

js쓰다가 c++로 넘어오니 ""와 ''의 차이 때문에 컴파일 에러가 많이 났다.

 

'알고리즘 문제풀이' 카테고리의 다른 글

[백준] 11403번 경로 찾기 c++  (0) 2022.12.25
[백준] 14500 테트로미노 c++  (0) 2022.12.24
[백준] 5525번 IOIOI c++  (0) 2022.12.10
[백준] 5430번 AC c++  (0) 2022.12.10
[백준] 10026번 적록색양 c++  (0) 2022.11.30

아래 코드와 같이 컨테이너 안 어느 요소든 탭했을때 textfield의 focus를 null로 만들어 키보드를 내리는 기능을 구현하려고 했다.

 

문제의 코드

 

컨테이너 안 빈공간을 눌러도 키보드가 닫히지 않았다.

 

하지만 신기하게 컨테이더 안에 circle 요소나 text를 클릭하면 기능이 동작을 했다.

그래서 container가 할당되는 공간이 없는 건가 싶어서 color를 넣어 확인했지만 이미 container를 가득 채운 상태였다.

더 이상한 것은 color를 넣으니 원래 의도하던대로 기능이 잘 작동한다는 것이었다.

 

혼란에 빠져 구글링을 하다가 내가 겪은 문제를 다룬 깃헙이슈를 발견했다.

 

[GestureDetector with Container not working without color]

https://github.com/flutter/flutter/issues/32364

 

GestureDetector with Container not working without color · Issue #32364 · flutter/flutter

Hey, I don't know if this is the expected behavior but a GestureDetector with a Container in it's child, the onTap callback is not executed when the Container doesn't have a color. For ...

github.com

 

 

결론은 container의 color를 따로 지정하지 않으면 container의 decoration이 null로 설정되어서 tap을 인식하지 못한다고 한다.

 

해결방법

1. color를 넣어준다.

2. color를 넣지 않아야하는 경우라면 Colors.transparent(투명)를 넣는다.

 onTap이 잘 작동할 것이다.

Link태그와 onclick navigate를 함께 사용할 경우 navigate가 동작하지 않는 현상

 

글을 삭제한 후에  다시 글 목록으로 돌아가는 기능을 구현하려고 했다.

Link 태그의 to와 별개로 onclick의 handler를 두어서 다른 작업을 처리한 후에 navigate하는 기능을 만들었지만 동작하지 않았다. handler에서 분명 navigate 달아줬음에도 페이지가 그대로 멈춰있었다.

 

원인은 다음과 같다.

Link테그는 a테그를 기반으로 한다. Link태그를 클릭했을때 onclick function이전에 a테그의 direction만 적용되고 onclick 안에 선언한 navigate는 실행되지 않았다. 나의 경우 Link태그에 to 자체를 설정하지 않았기 때문에 페이지에 그래도 잔류하게 되었던 것이다.

 

해결방법

이를 해결하기 위해서는 html 자체의 동작을 막아주는 event.preventDefault()를 이용해야 한다. onclick에 들어가는 콜백함 수 안에 event.preventDefault() 선언해줌으로서 Link 태그의 동작을 막을 있다.

event.preventDefault()는 a태그 뿐 아니라 form태그에서 submit을 누르는 경우 새로고되는 등의 현상을 막기위해 사용되기도 한다.

 

 

const deleteHandler = (e) => {
    e.preventDefault();
    dispatch(deleteEvent(event));
    navigate("/event", { replace: true });
};
  
  
<Link onClick={deleteHandler}>삭제하기</Link>

 

사실 그냥 button태그를 쓰면 되는데 실수로 태그를 안바꾸고 구현하다가 처음 겪는 현상을 마주해서 알아보았다.

+ Recent posts