본문 바로가기
코드이그나이터와 php7와 mysql

크레이의 라라벨 도전기 #6. 마이그레이션

※ 이 게시글은 크레이의 IT개발 관련 성장기를 다루고 있습니다. 관련지식이 약간 있어야 이해되실 수 있습니다. 가벼운 마음으로 읽어보시면서 흥미가 생기고 의욕이 생긴다면? 개발자의 자질이 있으신 겁니다 :)

철새들이 떼지어 서식지를 옮기는 일이 있는데요. 이를 마이그레이션이라고 합니다.

프로그래밍 실무에서 마이그레이션(migration)은 서비스를 좀 더 나은 환경으로 이전하는 것을 의미하기도 합니다. 그런데 이는 보통 쉬운 작업이 아닐 경우가 많습니다.

마이그레이션은 대개 둘 중 하나를 의미하는데요.
데이터베이스를 상위 버전으로 마이그레이션하거나,
PHP와 같은 웹언어 프로그램 버전을 5에서 7로 바꾸는 것이지요.
물론 프로그램 언어 자체를 변경하는 경우도 있긴 한데,
이 때는 '신규개발'이라는 명칭을 사용하는게 어울립니다.

이를테면 한글2019버전에서 작업한 문서는 그보다 상위 버전인 한글 2020 버전에서 잘 열리기 마련인데요.
이는 하위 호환성이라 하여 낮은 버전의 모든 기능을 지원하기 때문입니다.

하지만 프로그래밍 세계에서는 대부분 하위 호환성을 보장하지 않는데요,
일명 'deprecated' 라고 하여 멀쩡히 사용하던 내장함수가 상위 버전에서 갑자기 없어지는 경우도 있고,
데이터베이스 테이블에서 필드명으로 사용하고 있던 항목이 예약어로 바뀌면서 발생하는 엉뚱한 오류가 생겨나기도 합니다. 문법적으로 오류는 없으나 'mysqlDB' 에서 빠르게 작동하던 '서브쿼리'가  상위 버전 'mariaDB' 에서는 사용하지 못할 정도로 급격한 속도 저하가 생기기도 하지요.

필자가 2000년경 업무에 사용했던 프로그래밍 언어 비주얼 베이직 6.0은 현재 윈도우 10에서는 아예 설치 조차 되지 않는데요. 이러한 경우 새로운 프로그래밍 언어로 다시 개발해야 합니다.
빠르게 변화하는 세상 가운데 특히나 IT 기술분야는 그 변화가 매우 급속히 이루어 집니다.

마이그레이션은 늘 위험을 동반하지만 '새 술은 새 부대에' 담아야 한다는 성경 말씀처럼 최근 IT 기술 분야에 맞게 마이그레이션 또한 계획을 가지고 이루어지는 것이 좋습니다. 이는 결코 단시간에 수행하려는 무리한 계획을 가져서는 안됩니다. 사고가 발생하기 때문이지요.

라라벨의 마이그레이션 기능은 필자의 경험에 기초해 볼 때 약간 생뚱맞은 느낌이 듭니다.
물론 그 기능은 훌륭하지만요. 용어를 붙인 이의 의도를 생각하면 다른 종류에 데이터베이스로 쉽게 옮겨갈 수 있다는, 이를테면 mysql에서 mssql로 바꾸기에 쉬운 것과,
또 하나는 git 시스템을 채택하여 사용하는 경우 소스코드는 변화 이력이 남으나 데이터베이스는 변화이력이 남지 않는데요. 라라벨의 마이그레이션은 이런 변화이력을 일목 요연하게 남길 수 있도록 그 방법을 가이드하고 있습니다. 이런 DB의 변화 과정(마이그레이트)을 기록한다는 의미에서 용어를 사용했을 것이라는게 필자의 견해입니다.

라라벨의 마이그레이트에 대해 전문용어가 아닌 쉬운 말로 간단히 요약하자면 'DB 이력관리 및 자동화'입니다.
라라벨 프레임 워크 개발자의 의도는 그쪽에 가까운 것 같습니다.


사연이 좀 길었군요 :)
이제 라라벨의 마이그레이션에 대해 알아낸 부분 공유드립니다.
어떠한 경우에도 데이터베이스는 처음에 한번 구성하고 끝이 아닙니다.

오히려 장기간 서비스를 거치면서 무수히 많은 변화를 거치게 되는데요.
이러한 변화 이력을 잘 관리할 수 있다면 어떨까요? 그것도 날짜 단위로 말이지요
의지를 가지고 DB 변화에 대한 기록을 남긴다면 가능하겠지만 쉬운 일은 아닐 텐데요.
라라벨은 도리어 이러한 이력관리를 하면 편리한 기능을 제공합니다.
그렇기에 약간의 귀차니즘만 적응하면 됩니다.
그러니 사용법을 익혀두면 자연스러운 이력관리가 가능한 것이지요.

자, 이제 우리는 하나의 서비스를 계획하고 있다고 가정합시다. 이른바 '미래박스' 서비스인데요.
미래박스 서비스에 글이나 그림을 남기면 기록 이후 일주일 뒤 사람들에게 그 내용이 오픈되는 것이지요.
새로운 박스가 올라오면 사람들이 무얼까 일주일동안 궁금해 하다가 일주일 뒤에 글의 내용을 보게 되는 뭐 그런 시스템입니다. 많이 사용할지는 미지수입니다 :)

미래박스 컨텐츠에 기록될 요소는 무엇일까요?
회원 가입 그런건 생각하지 말고 단순히 제목, 본문, 글쓴이별명, 패스워드, 글쓴 날짜, 오픈 날짜, 조회수 
이 정도만 생각해 봅시다. 패스워드는 글 쓴이 본인만이 수정/삭제할 수 있는 하나의 보안 수단입니다.

제일 처음 테이블을 생성해야겠지요.
이 때 테이블 작성 기록을 남기기 위해서는 DB툴을 사용하는 것이 아니라,
라라벨의 테이블 생성 기능을 이용해야 합니다. 이력 관리는 여기서부터 시작하는 것입니다.

순서는 아래와 같습니다.

1) 라라벨! 테이블을 생성할 소스 코드를 만들어줘!
2) 소스 코드 파일을 열어 필드 규칙을 정의합니다. 이 부분은 직접 해줘야 합니다. 라라벨은 인공지능은 아니니까요.
3) 라라벨! 규칙대로 테이블을 만들어줘!

웬지 라라벨이 친근해 보이지 않으신가요? :)

1)의 과정은 아래와 같습니다.

테이블명은 box 로 정하기로 합시다.
라라벨의 우아한 엘로퀀트를 사용하려면 테이블명은 단수가 아닌 복수로 지어주어야 합니다.
라라벨 튜토리얼에는 뒤에 단순히 s 를 붙여주는 걸로 되어 있는데요.
box 라는 이름을 테이블명으로 정할 경우 이렇게 해서는 안되고 영문법에 맞춰서 복수 이름을 지정해 주어야 작동합니다. boxes 로 말이지요 :)

그 외에도 reply 는 영문법에 맞게 replies 와 같은 방식으로 지어주어야 한다는군요.
이 부분은 아직 실험해보지는 않았습니다.

이제 리눅스 터미널에서 아래 artisan 명령어를 실행하면 테이블을 생성할 소스코드를 만들어줍니다.

php artisan make:migration create_boxes_table

테이블을 생성하는 경우 아래와 같이 이름을 지어 주어야 하는 점에 유의해 주세요.
'create_' + 테이블명(복수) + '_table'

그리면 라라벨 프로젝트 폴더 하위 database/migrations 폴더에 금방 만들어진 코드 파일이 보일텐데요

이 파일을 열어보면 아래와 같이 초기 소스 코드가 자동으로 생성되어 있습니다.

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateBoxesTable extends Migration
{
    public function up()
    {
        Schema::create('boxes', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('boxes');
    }
}

이제 up 메소드 안에 정의된 코드를 수정하면 됩니다.

public function up()
{

    :
}

라라벨의 엘로퀀트는 테이블에 기본으로 id라는 일련번호 필드를 수정이나 삭제의 기준 대상으로 삼는 특성이 있습니다. 아래 코드가 이 부분을 의미하는데요. 기본 값 그대로 사용하는 것이 좋습니다.

$table->bigIncrements('id');

또한 데이터가 입력된 날짜/시간(created_at), 데이터가 수정된 날짜/시간(updated_at)을 자동으로 보관해주는데요. 아래 코드가 이 부분에 해당한다고 할 수 있겠습니다. 이 역시 그대로 두는 것이 좋습니다.

$table->timestamps();

그러니 이 두줄의 코드 사이에 규칙이 되는 코드를 입력해주면 되는 것이지요.
그것이 2)번의 과정입니다.

아래처럼 코드를 입력하여 규칙을 정해주면 되는데요.
뭐 실수로 잘못 입력했더라도 상관은 없습니다.
다시 원래대로 돌리고 실행해주는 기능이 있으니까요.

$table->bigIncrements('id');

// 추가
// 제목, 본문, 글쓴이별명, 패스워드, 오픈 날짜, 조회수
$table->string('title', 100); // 필드명은 title, 글자수는 100글자로 정의
$table->text('body');          // 필드명은 body,  글자수는 DB 시스템의 text 글자수 기준 ( mysql  - 64kbyte )
$table->string('writer', 50);
$table->string('pwd', 60);
$table->datetime('opendate');  // 필드명은 opendate, 날짜/시간 값 입력
$table->integer('clicks')->default(0);  // 필드명은 clicks, 숫자만 입력

$table->timestamps();

글 쓴 날짜/시간은 기본으로 보관되니 굳이 필드를 잡아주지 않아도 됩니다.
필드명은 created_at 입니다.

이제 3)번 과정, 규칙에 따라 테이블을 생성해줄 일만 남았습니다.
다시 리눅스 터미널로 돌아와 아래 명령어를 실행하면 되는데요.

php artisan migrate

코드를 정상 입력했다면 위 결과가 노출되지만 잘못 입력했다면 오류가 발생합니다.
그 때는 2)과정의 코드 수정 후 3)을 다시 실행해주면 됩니다.
마이그레이션 명령어는 여러번 실행해도 한번만 액션을 수행하기 때문에 긴가 민가 할 경우 몇번을 다시 실행해도 괜찮습니다.

3)과정을 실행하니 아래와 같이 테이블이 짜잔! 정상으로 실행된 것을 알 수 있습니다.


세상 일이 한번에 될 수가 없듯이 만약 3)번의 과정에서 실수가 있었다고 합시다.
이를 테면 이를테면 작성자의 이메일 주소를 추가해야 하는 상황이라면 어떻게 해야 할까요?

2가지 방법이 있는데요.

우선 첫번쩨 방법입니다.
아직 데이터를 하나도 입력하지 않은 경우 테이블을 삭제하고 2), 3) 과정을 다시 실행하는 것입니다.
이 때는 테이블을 생성한 것을 되돌린다는 의미에서 터미널에서 아래 명령을 실행하면 됩니다.
(되돌리기! '로~올 빽'이라고 부릅니다:) )

php artisan migrate:rollback

그러면 down 메소드 내의 아래 코드가 실행되어 테이블이 바로 삭제됩니다.

public function down()
{
    Schema::dropIfExists('boxes');
}

그리고 나서 코드를 수정 후 앞의 3)번을 다시 실행하면 되는데요.
이 방법은 기존에 데이터가 확실히 없다는 가정하에 진행해야 합니다.

만일 테이블에 고객의 자료가 들어 있다면 이 과정을 진행하면 고객 데이터가 모두 삭제되기 때문에
절대로! 실행하면 안됩니다. ( 백업을 받아두었다 복구하는 경우는 예외입니다. )

라라벨이라 특정할 순 없지만 좋코딩 개발자 드라마에 비슷한 상황이 등장하는데 한번 시청해보시면 재미있으실 겁니다 :)

https://www.youtube.com/watch?v=89THSS5H5iE&list=PLU9-uwewPMe0x2TCSLEhFsi8ThPdJfXG0&index=4 

이처럼 데이터 롤백은 항상 신중해야 합니다.

그보다는 이러한 경우 2번째 방법이 있습니다.
바로 이메일 필드를 새로 추가해주는 이력을 정의하는 것이지요.

그렇게 진행한다면 기존 데이터는 전혀 건드리지 않아 안전합니다.
boxes 테이블에 필드를 추가할 경우 아래 명령어를 실행하면 되는데요.

php artisan make:migration add_name_to_boxes_table

필드를 추가할 경우 파라미터는 아래 형식을 갖는 점에 유의하면 됩니다.

"add_name_to_" + 테이블명 + "_table"

그러면 프로젝트폴더/database/migrations 폴더에 파일이 하나 추가된 것을 확인할 수 있는데요.

이 파일을 열어보면 코드가 들어 있긴 한데 앞에서 테이블을 생성한 것과 스타일이 다릅니다.

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddNameToBoxesTable extends Migration
{
    public function up()
    {
        Schema::table('boxes', function (Blueprint $table) {
            //
        });
    }

    public function down()
    {
        Schema::table('boxes', function (Blueprint $table) {
            //
        });
    }
}


email 필드를 순서상 어디에 추가하록 할까요?
writer 필드 다음에 위치한다고 합시다.

그렇다면 up 메소드를 아래와 같이 수정하면 됩니다.

public function up()
{
    Schema::table('boxes', function (Blueprint $table) {
            
        // 추가
        $table->string('email', 100)->after('writer')->nullable();

    });
}

만일 마이그레이션을 rollback 하는 상황을 가정한다면, down() 메소드도 아래와 같이 구현하여
필드를 삭제하는 상황을 만들면 되나, rollback 은 불안정하기 때문에 추천하지 않습니다.
( 이 과정에서 2단계의 롤백이 실행되거나 하면 테이블이 삭제될 수 있습니다. )

public function down()
{
    Schema::table('boxes', function (Blueprint $table) {
            
        // 비추
        $table->dropColumn('email');

    });
}

이제 다시 터미널에서 마이그레이션을 실행합니다.
그러면 금방 추가한 코드가 실행되어 마이그레이션이 실행됩니다.

php artisan migrate

email 필드도 제대로 추가된 것을 볼 수 있지요.

만일 필드명을 변경하려면, 이를테면 email 을 usermail 로 바꾸려면 아래 명령으로 코드 파일을 생성하고,

php artisan make:migration update_name_to_boxes_table

역시나 생겨난 소스 코드에서 아래 코드를 추가하면 되는데요.

public function up()
{
    Schema::table('boxes', function (Blueprint $table) {
            
        // 추가
        $table->renameColumn('email', 'usermail');

    });
}

마이그레이션을 실행하면 십중팔구 오류가 발생합니다.

php artisan migrate

이 것은 Doctrine 이라는 모듈이 기본 설치가 되지 않았기 때문이지요.
이 때는 모듈 관리자인 컴포저를 가동해 Doctrine을 설치해주면 됩니다.

프로젝트폴더/composer.json 파일을 열어 require 섹션에 아래 코드를 추가하고 저장해줍니다.

      :
    "require": {
        "php": "^7.1.3",
        "fideloper/proxy": "^4.0",
        "laravel/framework": "5.8.*",
        "laravel/tinker": "^1.0",
        "doctrine/dbal": "v2.4.2"
    },
      :

그리고 터미널에서 컴포저를 업데이트하면 doctrine 모듈을 설치해 줍니다.
이 과정은 프로젝트당 한번만 해주면 됩니다.

composer update

그리고 나서 다시 마이그레이션을 실행하면,

php artisan migrate

정상실행되는 것을 확인할 수 있습니다.

테이블도 이렇게 정상적으로 이름이 변경되었구요.
샘플로 데이터를 사전 입력해두고 테스트해보았는데요. 데이터도 그대로 살아있는 것을 확인할 수 있습니다.

라라벨 튜토리얼에는 없는 내용이지만 크레이가 하나 조언을 드리자면, 롤백코드는 되도록 사용하지 않는 것이 좋은데요. 편한걸 좋아하다 된통 얻어맞을 수도 있기 때문입니다. 위의 드라마에서 보셨던 갑자기 데이터가 모두 유실되는 사태 말입니다.

크레이라면 이렇게 하겠습니다.
제일 처음 생성된 테이블 생성 코드에서 롤백 메소드의 테이블 삭제는 주석처리해서 실행하지 않도록 합니다.
이 경우 롤백 단계를 실수로 수행해도 테이블이 날아가는 일은 발생하지 않습니다.
테이블을 삭제할 일이 있다면 DB 툴에서 수동으로 확인하면서 삭제하는 것이지요.

public function down()
{
    //Schema::dropIfExists('boxes');
}

오늘은 라라벨의 마이그레이션에 대해 알아보았는데요.
다음 번에는 이렇게 마이그레이션을 통해 생성한 테이블을 엘로퀀트로 다루는 부분을 살펴보도록 하겠습니다.

아무쪼록 필요하신 분께는 도움이 되셨기를,
그리고 여전히 방문해 주시는 모든 분들께 감사드립니다.


성경말씀 한 구절 공유드립니다. 크레이가 믿는 예수 그리스도께서 성경을 통해 하신 말씀입니다.
만일 이 말씀이 믿어지신다면 독자분께서는 복된 자라고 크레이가 감히 말씀드릴 수 있겠습니다 :)

"내가 진실로 진실로 너희에게 이르노니 
내 말을 듣고 또 나 보내신 이를 믿는 자는 
영생을 얻었고 심판에 이르지 아니하나니 
사망에서 생명으로 옮겼느니라."
- 요한복음 5:24 -