본문으로 건너뛰기

Vue 선언된 data에 chiledren 추가시 렌더링이 안될 때

· 약 2분

data가 이미 정의 되어있고 나중에 데이터를 추가하면 observer가 생성되지 않아 데이터가 갱신이 되어도 DOM이 업데이트가 안 된다.

템플릿

<div id="memberList">
<div v-for="member in members">
{{ member.name }}
<ul v-if="member.logs && member.logs.length > 0">
<li v-for="log in member.logs"></li>
</ul>
</div>
</div>

JS

new Vue({
el: "#memberList",
data: {
members: [{ id: 1, name: "gracefullight" }],
},

mounted: function () {
/* member의 logs 데이터는 그냥 배열로 선언된다. */
this.members[0].logs = [];
/* 데이터를 넣어도 위 템플릿의 <li> 부분이 반복되지 않는다. */
this.members[0].logs = [
{ id: 1, message: "test action", created_at: "2017-11-22" },
];
},
});

해결

set 메소드 또는 $forceUpdate 메소드를 사용하면 된다.

/* 1안 */
const option = {
mounted: function () {
this.$set(this.members[0], "logs", []);
this.members[0].logs = [
// ...
];
},
};

/* 2안 */
const option = {
mounted: function () {
this.members[0].logs = [];
this.members[0].logs = [
// ...
];
this.$forceUpdate();
},
};

adonisjs 시작하기 (nodejs framework)

· 약 8분

nodejs로 정말 간단한 oauth2, jwt 등의 인증을 사용하지 않는 API를 만들기에는 express가 정말 딱이다.

하지만 조금 더 깔끔한 코드를 원하거나 새롭지만 반복적인 기능을 넣기 위해선 더 큰 프레임워크가 필요했다.

조건

언제나 개발시간은 부족하기에 내가 필요한 기능들을 정리해 보았다.

  • await, async를 아무 세팅 없이 사용해 Promise chaining에서 벗어나고 싶다. (BabelWebpack을 직접 세팅하는 시간 걸리는 짓을 안하고 싶다.)
  • 기본적인 웹 보안기능 (validate, csrf, xss, injection) 및 API Throttle Request 기능이 있어야한다.
  • 소셜 로그인passport를 사용하지 않고 한 줄로 끝내고 싶었다.
  • JWT 인증, Log 기능, Bcrypt 암호화를 직접 붙히고 싶지 않다.
  • ORM에 맞게 E-commerce DB를 구조화했기 때문에 ORM이 꼭 필요하다.
  • 직관적이여야 한다. (Model과 Controller가 완벽히 나뉘는 구조를 원했다.)
  • 문서화가 완벽해야한다.
  • 커뮤니티Github Issue가 활성화 되어있어야한다.
  • 관리자 기능을 SSR로 해야할 가능성이 있어 템플릿 기능이 있어야하며, ejs는 절대 쓰고 싶지 않고 다른 템플릿을 직접 세팅해야하는 일은 더더욱 없어야한다.

이 조건을 거르니 adonisjs 라는 처음 보는 프레임워크가 눈앞에 있었다.

성능

image from hexo

나쁘지 않다. 어차피 nginx에서 reverse proxy 처리에, auto scaling 걸꺼고 그래도 더 빠른 속도를 원하면 golang으로 가야지 왜 이 포스트를 보고있나!

  • 여기서 자세한 내용을 확인할 수 있다.

평판

이 프레임워크를 쓰는 다른 사람은 어떻게 생각할까도 궁금했다.

Node.js broken ecosystem and rise of AdonisJs란 포스팅이 마음을 사로잡았다. 원문 읽을 시간이 없다면 이 글의 요약은 이 것이다.

Why not use Express + Sequelize + Config manager + Passport + dotEnv + NodeMailer + Node Validator +30 other modules. How assembling 100+ modules manually can be better than a beautifully pre-configured framework?

한글로 번역하면 왜 안 쓰시죠? 정도가 되시겠다.

시작하기

요구사항

  • Node >= 8.0
  • NPM >= 3.0

1711 기준 8.9가 LTS 버전이기 때문에 그냥 쓰면 된다.

Cli 설치

요즘 멋진 프레임워크들은 다들 cli를 가지고 있다.

$ yarn global add @adonisjs/cli

앱 설치

$ adonis new 프로젝트명
$ cd 프로젝트명
$ adonis serve --dev

localhost:3333에서 서버가 돌아간다. image from hexo

폴더 구조

.
├── ace
├── package.json
├── public
├── server.js
└── start
├── app.js
├── kernel.js
└── routes.js
파일/폴더설명
aceadonis에서 제공하는 cli 명령어 툴
package.json-
publiccss, js, images와 같은 public 리소스
server.jsHTTP 서버를 부트스트래핑한다. .env 파일의 PORT 변수를 따른다.
start/app.jsadonis 실행시 필요한 기능들을 부트스트래핑한다.
start/kernel.js미들웨어를 등록한다.
start/routes.js라우팅

라우팅

start/routes.js에서 등록한다.

메소드

Route 뒤에 method를 붙히면 된다.

Route.get("/boards", async () => {});

Route.get("/boards/:id", async ({ params }) => {
const board = await Board.find(params.id);
return board;
});

컨트롤러와 연결

Route.get("boards", "BoardController.index");
/* 리소스로 사용시에 */
Route.resource("boards", "BoardController");

/* 리소스에서 create, edit 메소드를 빼고 사용시 */
Route.resource("boards", "BoardController").apiOnly();

/* 구미가 당기는 것만 사용시 */
Route.resource("boards", "BoardController").only(["index", "destroy"]);

/* 구미가 안 당기는 것을 제외할 시 */
Route.resource("boards", "BoardController").except(["index", "destroy"]);

그룹화

보통은 기능이 비슷한 것 끼리 그룹화를 해서 가독성을 높인다.

/* /api/boards routes */
Route.group(() => {
Route.get("/boards", "BoardController.index");
Route.post("/boards", "BoardController.store");
}).prefix("api");

컨트롤러

cli에서 쉽게 생성이 가능하다.

$ adonis make:controller BoardController

리소스 컨트롤러

리소스가 대단한 건 아니고 미리 정의 된 메소드 7개로 RESTful API를 빠르게 만들기 위한 것이다.

$ adonis make:controller BoardController --resource

각 메소드들이 연결되는 건 다음과 같다.

  • index: GET boards
  • create: GET boards/create
  • store: POST boards
  • show: GET boards/:id
  • edit: GET boards/:id/edit
  • update: PUT boards/:id
  • destory: DELETE boards/:id

모델

하나의 모델은 하나의 테이블과 매칭된다고 보면 된다.

$ adonis make:model Board

모델 구조

모델 생성 후에 몇 가지 설정을 해줘야한다.

Board.js
class Board extends Model {
// 테이블 명을 변경해야할 경우
// (테이블 명이 모델명의 복수형이 아닐 경우)
static get table() {
return "board";
}

// 기본 커넥션이 변경될 경우
static get connection() {
return "mysql";
}

// PK 컬럼명이 id가 아닐 경우
static get primaryKey() {
return "uid";
}

// PK의 Auto Increment가 아닐 경우
static get incrementing() {
return false;
}

// 비밀번호 같은 컬럼을 보여주지 않아야 될 경우
// 이 경우 모델에서 fetch 또는 first 메소드로 쿼리빌더를 실행해야된다
static get hidden() {
return ["password"];
}

// 생성일 컬럼이 변경될 경우
static get createdAtColumn() {
return "created_at";
}

// 업데이트일 컬럼이 변경될 경우
static get updatedAtColumn() {
return "updated_at";
}
}

위 속성을 제외하고는 문서를 참조해보자.

기존 코드를 내려받을 때

.env.example 파일을 .env로 복사해 환경설정을 해주고 로그를 남기기 위해 tmp 폴더를 생성해준다

/
$ cp .env.example .env
# .env 파일을 수정하고

$ mkdir tmp

여담

이 프레임워크는 라라벨 스타일로 만들어졌기 때문에 (개념이 같다) 모던 PHP 개발자가 되는 건 식은 죽 먹기가 될 수 있다.

Windows에서 환경변수 cmd로 등록하기

· 약 1분

매번 내 컴퓨터 > 설정 > 고급 설정 > 환경 변수에 들어가는 걸 그만하고 싶은 사람이라면 다음과 같이 하면 된다.

$ setx path "%path%;새로운 경로"

$ refreshenv

설명

환경변수를 등록하고 그 변수를 반영한다.

Linux startup 파일에서 피해야할 것

· 약 2분

스타트업 파일은 사용자가 로그인 할 때 시스템이 어떻게 반응해야 하는지를 결정한다. 스타트업 파일 수정시에 다음 사항들을 꼭 피해야한다.

  • 셸 스타트업 파일에 그래픽 명령을 넣지 않는다.
  • 셸 스타트업 파일에 DISPLAY 환경 변수를 설정하지 않는다.
  • 셸 스타트업 파일에 터미널 유형을 설정하지 않는다.
  • 스타트업 파일에서 표준 출력으로 인쇄하는 명령을 실행하지 않는다.
  • 셸 스타트업 파일에 LD_LIBRARY_PATH를 결코 설정하지 않는다.

LD_LIBRARY_PATH 변수 조작시 런타임 링커가 모든 프로그램에 대해 이 디렉터리들을 찾기 때문에 충돌을 일으킬 수 있고, 라이브러리의 조합이 틀어질 수 있기 때문이다.

추가적으로 디폴트 스타트업 파일에 상세한 주석을 충분히 첨부한다.

Laravel 5.5 - Model Event Listener

· 약 3분

라라벨 이벤트 리스너 기능을 붙혀보자. Model이 Create 될 때 이벤트 리스너를 붙혀 다른 기능을 연결하는 예제가 가장 쉽다. (예를 들면 로그가 생성될 때 SMS를 날리는 경우)

EventServiceProvider

먼저 EventServiceProvider에 내가 사용할 이벤트와 리스너를 등록해줘야한다.

app/Providers/EventServiceProvider
<?php
...
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
// 로그 생성시 이벤트를
'App\Events\LogCreated' => [
// 로그 생성됨 리스너에 연결시켜준다.
'App\Listeners\LogCreatedListener',
],
];

$listen 변수에 기본으로 등록되어있는 이벤트는 지워주자

generate

이제 소스 파일을 생성시켜준다.

$ php artisan event:generate

명령어를 실행하면 app/Eventsapp/Listeners에 방금 등록한 이벤트 리스너 파일이 자동으로 생성된다.

바인딩

모델

모델에서 방금 추가된 이벤트를 연결시켜주자.

app/Models/Log
<?php
use App\Events\LogCreated;

class Log extends Model
{
...
protected $dispatchesEvents = [
// 모델이 create(insert) 되면 해당 이벤트를 호출한다.
'created' => LogCreated::class
// use 구문을 사용하지 않고 여기에 직접 "App\Events\LogCreated" 로 정의해도 될 것 같은데 테스트는 안 해봤다.
];
}

이벤트

이벤트에서 해당 모델을 연결시켜주자.

app/Events/LogCreated
<?php
use App\Models\Log;
...

class LogCreated
{
use Dispatchable, InteractsWithSockets, SerializesModels;

// 리스너에서 받을 모델 변수를 public으로 생성한다.
public $log;

// DI
public function __construct(Log $log)
{
$this->log = $log;
}

public function broadcastOn()
{
// 채널을 이용하지 않을 것이기에 빈 배열을 리턴시킨다.
return [];
}
}

처리

리스너에서 받은 이벤트를 처리하자.

app/Listeners/LogCreatedListener
<?php
...
use App\Events\LogCreated;

class LogCreatedListener
{
...
public function handle(LogCreated $event)
{
// Events의 public으로 선언한 데이터가 $event 아래로 바인딩 된다.

$log = $event->log;
logger('LOG Received');
logger($log);

// 여기서 기능을 구현하면 된다.
}
}

ShouldQueue로 확장해 큐에 담을 수도 있다.

여담

메일 발송과 비슷한 플로우였다.

Vue에서 jquery와 bootstrap 전역으로 사용하기

· 약 1분

expose-loader의 설치가 필요 없는 방법을 사용해보자

Vuejs-kr에 좋은 내용이 있지만 웹팩을 통해 jquery를 꺼내는 방법이 더 간단하다.

$ yarn add jquery bootstrap

설정

webpack

build/webpack.base.conf.js
const webpack = require("webpack");

module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jquery: "jquery",
"window.jQuery": "jquery",
jQuery: "jquery",
}),
],
};

eslint

.enlintrs.js
module.exports = {
globals: {
$: true,
jQuery: true,
},
};

연동

src/main.js
import "bootstrap";

new Vue({});

Top 명령어 단축키

· 약 1분

top 명령어는 자주 치는 명령어지만 예쁘게 소팅하기란 쉽지 않다.

단축키내용
spacebar화면 즉시 업데이트
M현재 상주 메모리 사용량에 따라 분류
T전체 누적 CPU 사용량에 따라 분류
P현재 CPU 사용량(디폴트 값)에 따라 분류
c프로세스 경로 표시
u오로지 한 사용자의 프로세스만 보여줌
f다른 종류의 통계 자료가 나타나도록 선택
?모든 top 명령에 대한 사용법의 개요를 보여준다.

/etc/passwd 파일 구조

· 약 1분

로그인명:비빌번호:사용자 ID:그룹 ID:실제 유저명:홈 디렉토리:쉘

  • 예시: root0:0:superuser:/root:/bin/bash
  • 주석이나 공백은 허용되지 않는다.

비밀번호 필드

  • x 표시: 암호화된 비밀번호가 /etc/shadow 파일에 저장되어 있다는 것
  • * 표시: 로그인할 수 없는 사용자
  • 공백: 로그인시 비밀번호가 필요 없음

비밀번호 변경

  • passwd 명령어는 누구나 알지만
  • vipw 로 /etc/passwd 파일을 통째로 편집 가능