반응형

이토록 평범한 미래

오랜만에 소설책을 한권 읽었다. SNS에서 너무나도 많이 추천되고 있는 김연수 작가의 "이토록 평범한 미래".

 

김연수 작가의 8개 단편집을 묶은 소설집이다.
 
작가 특유의 느낌이 좋은 글이다. 약간 우울하기도 하지만 따스한 온기를 느낄 수 있다.
8개의 단편중 첫번째 작품의 제목이 "이토록 평범한 미래"이다.
그해 여름 자살을 꿈꾸는 지민, 그녀의 엄마는 작가였지만 일찍 세상을 떠났다.
유일한 작품의 제목은 "재와 먼지".
 
이 책은 "1972년 10월이 시간의 끝이다"라는 단 한문장 때문에 유신정권에 의해 출판 금지를 겪게 된다.
1972년 10월은 박정희 대통령에 의해 국회가 해산되고 헌법의 효력이 정지되는 유신이 발표된 때였던 것이다.
책의 내용은 동반자살을 한 연인이 죽지않고 미래에서 과거로 진행되는 새로운 인생을 살게된다는 내용이다.
처음 만났던 순간 그토록 설레고 기쁜 마음의 순간으로... 그리고 그 시간 후 다시 정상적인 시간의 흐름으로 세번째 삶을 살게 된다.
우리는 과거의 아름다웠던 기억들을 대부분 잊고 살아간다. 그 순간의 설레이고 소중한 감정들을 망각한채...
 
김연수 작가의 아름다운 표현들도 책을 읽는 즐거움을 더해준다.
  • 군락을 이룬 황하 코스모스가 일제히 한쪽으로 몸을 수그렸고...
  • 울음의 주도권은 울음이 쥐고 있었다.

 

작가는 후기에서 어떤 글을 쓰고 싶었냐는 질문에 다음과 같이 답한다.
메리 올리버는 "골든 로드"라는 시에서 "빛으로 가득 찬 이 몸들보다 나은 곳이 있을까?"라고 썼다.
이 경이로운 문장 이전에 무슨 일이 있었는지 나는 잘 알게 됐다. 직전의 시구는 다음과 같다.
"우리의 삶이라는 힘든 노동은 / 어두운 시간들로 가득하지 않아?"
'어두운 시간'이 '빛으로 가득찬 이 몸'을 만든다.
지금 내가 쓰고 싶은 이야기도 이런 것이다. 그리고 이 이야기들은 언제가 우리의 삶이 될 것이다.

 

이 작품에서는 쓸쓸함과 아련함이 느껴진다.
사랑에 대한 이야기들, 어쩌면 다른 말로는 쓸쓸함이라고 표현할 수 있지 않을까?
누구나 마음 한편에 품고 사는...
반응형
반응형

다음은 이 글의 동영상 버전입니다.

https://youtu.be/Geq3EUkzPuc 

 
우리 팀이 높은 성과를 내는 조직이 되기 위해서 가장 필요한 것은 무엇일까요?
 
회사가 성장하면서 끊임없이 하게 되는 질문입니다.
지난주 Accelerate 디지털 트랜스포메이션 엔진 이라는 책을 읽으며,
기존에 생각하고 있던 개발프로세스 개선과 새로운 영감들을 많이 정리할 수 있었습니다.
 
연구에 의하면 고성과 팀은 저성과 팀에 비해 다음과 같은 차이를 보였다고 합니다.
  • 46배나 더 자주 코드를 배포했다.
  • 계획에서 배포까지의 리드타임이 440배 빨랐다.
  • 다운타임에서 회복하는 평균시간이 170배 빨랐다.
  • 변화 실패율이 5배나 낮았다. 즉, 변화에 실패할 확률이 1/5이다.
 
그렇다면 고성과 팀이 되는데 가장 큰 영향을 미치는 것은 무엇일까요?
 
책에서 말하는 결론은,
Software delivery performance 즉, 전달 성과가 조직의 성과에 가장 큰 영향을 미친다고 합니다.
 
이 책은 DevOps 전문가인 Nicole Frosgren, Jez Humble, Gene Kim이 고성과 IT 조직에 대한 연구를 기록한 글입니다.
영문판의 제목은 Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations 입니다. 제목에서 처럼 Lean Software와 DevOps에 대한 내용을 주로 다루고 있습니다.
 
조직의 성과에 가장 큰 영향을 미치는 Software delivery performance를 높이기 위해서는 어떤 역량들이 필요할까요?
책에서는 여러가지 역량들을 소개하고 있고, 그것들은 다음과 같이 5가지 범주로 나눌 수 있었습니다.
  • Continuous Delivery (CD, 지속적 전달)
  • Architecture (아키텍처)
  • Product & Process (제품 및 프로세스)
  • Lean Management & Monitoring (린 관리 및 모니터링)
  • Culture (문화)
각 항목들의 세부사항들을 정리하면 다음과 같습니다.
 

Continuous Delivery (CD, 지속적 전달)

Continuous Delivery는 소프트웨어 Release를 자동화 하는 것을 의미합니다.
  • Version Control (버전 컨트롤)
  • 배포 프로세스 자동화
  • Continuous Integration (CI, 지속적 통합)
  • Trunk 기반의 개발
  • 테스트 자동화
  • 테스트 데이터 관리
  • 보안 통합
  • Continuous Delivery(CD, 지속적 전달)

Architecture (아키텍처)

  • Loosely Coupled
  • 권한이 부여된 팀

Product & Process (제품 및 프로세스)

  • 고객의 피드백을 수집하고 구현
  • 작업 흐름의 시각화
  • 소규모 배치
  • 팀 실험의 활성화

Lean Management & Monitoring (린 관리 및 모니터링)

  • 변경 승인 프로세스 간소화
  • 애플리케이션 & 인프라 모니터링
  • 시스템 상태를 사전에 예방
  • 프로세스를 개선하고 WIP를 제한
  • 품질 및 작업을 시각화

Culture (문화)

  • 학습을 장려
  • 팀 간 협업을 지원하고 촉진
  • 작업을 의미 있게 만드는 자원과 도구를 제공
  • 트랜스포메이션 리더십을 지원
 
책에서 소개한 내용들 중 우리 환경에 필요하고 맞는 부분을 찾아 잘 적용하면, 개발 Performance, 품질, 보안등 많은 부분에 큰 효과가 있을 것 같습니다.
이렇게 프로세스를 잘 다듬는다면, 시스템 개발과 운영에 더 효율적이고, 스트레스를 덜 받는 개발자의 복지가 될 수 있지 않을까요?
관심 있으신 분들은 꼭 한번 읽어 보시길 추천드립니다.

반응형
반응형

다음은 이 글의 동영상 강좌입니다.

https://youtu.be/Dr4OXDLGsmI

Nestjs 프로젝트를 Docker화 하면 Application을 손쉽게 배포할 수 있습니다.

 

Docker가 설치된 서버에 docker로 빌드된 이미지만 업로드 하고 실행하면 됩니다.

github actions, aws pipeline등을 사용하면 모든 배포 단계를 자동화 할 수 있습니다.

이런 자동화를 통해 프로젝트의 생산성을 획기적으로 높일 수 있고,
개발자를 배포의 늪에서 해방 시켜 업무의 만족도를 높일 수 있습니다.

 

사전 설치 프로그램

우선 다음 프로그램을 설치합니다.

  • Nodejs
  • Docker
  • Nestjs를 사용하기 위해 nest cli를 설치합니다.
npm i -g @nestjs/cli

 

프로젝트 생성 및 실행

Nestjs 프로젝트를 생성합니다.

nest new nest-docker

 

프로젝트를 실행합니다.

npm start

 

Dockerfile / .dockerignore 파일 작성

프로젝트 root 폴더에 "Dockerfile" 파일을 생성합니다.

  • docker image 빌드시 사용하는 설계도입니다.
FROM node:18
RUN mkdir -p /var/app
WORKDIR /var/app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD [ "node", "dist/main.js" ]

프로젝트 root 폴더에 ".dockerignore" 파일을 생성합니다.

  • container에 불필요한 파일들이 복제되는 것을 막기 위한 용도입니다.
.git
*Dockerfile*
node_modules

 

Docker 이미지 생성

다음 명령으로 docker 이미지를 생성합니다.

docker build . -t nest-docker

 

Docker 실행

다음 명령으로 docker를 실행합니다.

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

 

브라우저에서 확인해보기

브라우저에서 다음 url을 입력하여 확인합니다.

http://localhost:3000

다음과 같이 결과를 확인할 수 있습니다.

 

반응형
반응형

다음은 이 글의 동영상 강좌입니다.

https://youtu.be/yjhIxpR_-gE

연재 마지막 순서로 첫화면에 이미지 슬라이드를 같이 개발합니다.

 

우선 관리자에서 이미지를 5개 정도 업로드합니다.

이미지 슬라이드는 Bootstrap-vue의 Carousel 컴퍼넌트를 사용합니다. 

https://bootstrap-vue.org/docs/components/carousel

 

BootstrapVue

Quickly integrate Bootstrap v4 components with Vue.js

bootstrap-vue.org

Carousel 소스를 복사하여 다음과 같이 수정합니다.

pages/index.vue

<template>
  <div>
    <b-carousel
      id="carousel-1"
      v-model="slide"
      :interval="4000"
      controls
      indicators
      background="#ababab"
      img-width="1024"
      img-height="480"
      style="text-shadow: 1px 1px 2px #333;"
      @sliding-start="onSlideStart"
      @sliding-end="onSlideEnd"
    >
      <!-- Text slides with image -->
      <div v-for="slider in sliders" v-bind:key="slider.id">
        <b-carousel-slide
          caption="First slide"
          text="Nulla vitae elit libero, a pharetra augue mollis interdum."
          :img-src="slider.imageUrl"
        ></b-carousel-slide>
      </div>
      
    </b-carousel>

    <p class="mt-4">
      Slide #: {{ slide }}<br>
      Sliding: {{ sliding }}
    </p>
  </div>
</template>
<script>
export default {
  name: 'IndexPage',
  data() {
    return {
      sliders: '',
      slide: 0,
      sliding: null
    }
  },
  computed: {
    accessToken() {
      return this.$store.state.accessToken;
    }
  },
  mounted() {
    this.getSliders();
  },
  methods: {
    async getSliders(){
      const options = {
          headers: {
              Authorization: `Bearer ${this.accessToken}`
          }
      }
      const path = '/slider';
      await this.$axios.get(
        path,
        options
      ).then(response=> {
        this.sliders = response.data;
        console.log(this.sliders);
      }).catch(error => {
        console.log(error);
      })
    },
    onSlideStart(slide) {
      this.sliding = true
    },
    onSlideEnd(slide) {
      this.sliding = false
    }
  }
}
</script>

서버를 실행하면 다음과 같이 화면이 스크롤 됩니다.

이렇게 해서 이미지 슬라이드가 완성되었습니다.

 

Vue와 Nestjs를 공부하시는 분들에게 도움이 되었으면 좋겠습니다.

 

수고하셨습니다^^

반응형
반응형

다음은 이 글의 동영상 강의 입니다.

https://youtu.be/_bmxSSTzQsQ

지난 시간에 S3와 DB에 업로드한 파일을 조회하고 삭제하는 기능을 구현해 봅니다.


이미지 조회 기능 구현

slider-api 프로젝트에서 slider를 조회하는 api를 다음과 같이 추가합니다.

/src/slider/slider.controller.ts

@Get('')
async getSlider(): Promise<Slider[]> {
    return await this.sliderService.getSliders();
}

/src/slider/slider.service.ts

async getSliders(): Promise<Slider[]> {
    return await this.sliderRepository.find();
}

slider-front 프로젝트에 조회 기능을 추가합니다.

/pages/admin/index.vue

data()에 sliders 변수를 추가합니다.

data() {
    return {
        file1: null,
        bucketName: this.$env.S3_BUCKET,
        bucketRegion: this.$env.S3_REGION,
        identityPoolId: this.$env.S3_IDENTITY_POOL_ID,
        sliders: ''
    }
},

methods: 아래

async getSliders(){
    const options = {
        headers: {
            Authorization: `Bearer ${this.accessToken}`
        }
    }
    const path = '/slider';
    await this.$axios.get(
        path,
        options
    ).then(response => {
        this.sliders = response.data;
        console.log(this.sliders);
    }).catch(error => {
        console.log(error);
    })
},

이미지를 보여주기 위해서 bootstrap-vue의 card를 사용합니다.

https://bootstrap-vue.org/docs/components/card 

 

BootstrapVue

Quickly integrate Bootstrap v4 components with Vue.js

bootstrap-vue.org

card component 소스를 복사하여 다음과 같이 수정합니다.

/pages/admin/index.vue

<div>
    <b-row>
        <b-col>
            <div v-for="slider in sliders" v-bind:key="slider.id">
                <div>
                    <b-card
                        :img-src="slider.imageUrl"
                        img-alt="slider"
                        img-top
                        tag="article"
                        style="max-width: 20rem;"
                        class="mb-2"
                    >
                        <b-card-text>
                        </b-card-text>
                        <b-button variant="primary" @click="deleteSlider(slider.id)">삭제</b-button>
                    </b-card>
                    </div>
            </div>
        </b-col>
    </b-row>
</div>

조회 테스트를 합니다.

이미지가 보이지 않습니다.

원인은 이미지 파일이 잘못 올라갔기 때문이므로, 업로드 소스를 "Body: this.file1" 으로 수정합니다.

await s3.upload({
    ACL: 'public-read',
    Body: this.file1,
    Key: this.genDateTime()+'_'+this.file1.name
}

이미지를 다시 업로드 하면 다음과 같이 표시됩니다.

 

이미지 삭제 기능 구현

slider-api에 삭제 api를 추가합니다.

/src/slider/slider.controller.ts

@Delete('/:id')
async deleteSlider(@Param('id') id, @Request() requestAnimationFrame, @Response() res): Promise<any> {
    return await this.sliderService.deleteSlider(id);
}

/src/slider/slider.service.ts

import * as AWS from 'aws-sdk';

async getSlider(id: number): Promise<Slider> {
    const slider = await this.sliderRepository.findOne({where: {id}});
    if(slider){
        return slider;
    }else{
        throw new NotFoundException(`Slider with ID ${id} not found!`);
    }
}

async deleteSlider(id): Promise<any> {
    const slider = await this.getSlider(id);
    if(slider){
        const bucketName = process.env.S3_BUCKET;
        const region = process.env.S3_REGION;
        const IdentityPoolId = process.env.S3_IDENTITY_POOL_ID;
        AWS.config.update({
            region,
            credentials: new AWS.CognitoIdentityCredentials({
                IdentityPoolId
            })
        });
        const s3 = new AWS.S3({
            apiVersion: '2006-03-01',
            params: {
                Bucket: bucketName
            }
        });
        //s3 파일 삭제
        const uriName = decodeURI(slider.imageUrl);
        const fileNamePos = uriName.indexOf('/images/');
        if(fileNamePos === -1){
            throw new NotFoundException();
        }
        const fileName = uriName.substring(fileNamePos + 8);
        s3.deleteObject({
            Bucket: bucketName,
            Key: 'images/'+fileName
        }), (err) => {
            if(err) {
                console.log(err);
                throw new InternalServerErrorException(err);
            }
        }
        //table에서 삭제
        return await this.sliderRepository.delete(id);
    }
}

터미널에서 다음 명령어로 aws-sdk 패키지를 추가합니다.

yarn add aws-sdk

환경 설정 파일 .env에 다음 내용을 추가합니다.
.env

S3_BUCKET=codegear-slider-bucket
S3_REGION=ap-northeast-2
S3-IDENTITY_POOL_ID=ap-northeast-2:*****************************

slider-front 프로젝트에 delete method를 추가합니다.
/pages/admin/index.vue

<b-button variant="primary" @click="deleteSlider(slider.id)">삭제</b-button>
.....
async deleteSlider(id){
    const headers = {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.accessToken}`
    }
    await this.$axios.delete(
        `/slider/${id}`,
        {headers}
    ).then(async response =>{
        await this.getSliders();
    }).catch(error=> {
        console.log(error);
    })
}

삭제 버튼을 클릭하면 이미지가 삭제되는 것을 확인할 수 있습니다.

 

반응형

+ Recent posts