위키의 핵심 기능은 과거의 모든 변경 내역을 조회할 수 있고, 원하면 과거 버전으로 쉽고 되돌아갈 수 있는 것이라 생각한다.
간혹 위키 같이 과거의 변경 내역을 기록으로 남기고 조회하는 기능이 필요할 때가 있다. 내가 운영하는 카페에서는 사물함 관리에 이 기능이 필요했다. 사물함 대여자 정보를 업데이트 할 때 실수를 할 수 있으므로, 변경되기 전의 데이터가 어딘가에 남아있어야 했다. 당시에는 단순하게 똑같은 테이블을 하나 더 만들어서(lockers
테이블과 똑같은 lockers_logs
테이블을 만드는 식으로) 업데이트 전에 백업하는 식으로 구현했다.
이런 기능이 내게만 필요했던 건 아니었는지 크리스 듀엘(Chris Duell)이 Revisionable이라는 패키지를 만들었다. 이 패키지를 사용하면 모델에 변화가 있을때마다 자동으로 ‘누가’, ‘무엇을’, ‘언제’, ‘어떻게’ 수정했는지가 저장된다. 수정내역을 남기고 싶은 모델에 RevisionableTrait
트레이트를 사용한다고 선언하기만 하면 된다. 사물함 관리 기능 만들 때 이 패키지가 알았더라면 ㅠ
<?php namespace App; use Illuminate\Database\Eloquent\Model; use Venturecraft\Revisionable\RevisionableTrait; class Post extends Model { use RevisionableTrait; }
실전에서 어떻게 쓰이는지 보는게 가장 와 닿을 것 같다. 내 카페용 애플리케이션에 관심있는 사람들은 없겠지만 겸사겸사 한 번 적용해보겠다.
설치
컴포저로 설치한다.
composer require venturecraft/revisionable
설치가 완료되면 config/app.php
파일의 providers
항목에 RevisionableServiceProvider
를 등록한다.
'providers' => [ Venturecraft\Revisionable\RevisionableServiceProvider::class, ]
설정 파일과 마이그레이션 파일을 퍼블리싱한다.
php artisan vendor:publish --provider="Venturecraft\Revisionable\RevisionableServiceProvider" // 제대로 진행된다면 아래와 같은 결과 메시지가 터미널에 출력된다. Copied File [/vendor/venturecraft/revisionable/src/config/revisionable.php] To [/config/revisionable.php] Copied Directory [/vendor/venturecraft/revisionable/src/migrations] To [/database/migrations] Publishing complete.
Revisionable 패키지는 모든 모델의 수정내역을 하나의 테이블로 관리한다. 마이그레이션을 실행한다.
php artisan migrate
데이터가 어떻게 저장되는지 확인하기 위해 마이그레이션 파일을 살펴보자.
public function up() { Schema::create('revisions', function ($table) { $table->increments('id'); $table->string('revisionable_type'); $table->integer('revisionable_id'); $table->integer('user_id')->nullable(); $table->string('key'); $table->text('old_value')->nullable(); $table->text('new_value')->nullable(); $table->timestamps(); $table->index(array('revisionable_id', 'revisionable_type')); }); }
revisionable_type
은 수정한 모델의 타입이고 revisionable_id
는 수정한 모델의 ID 값이다. 이런 방식으로 하나의 테이블에서 모든 모델의 변경사항을 다루는 걸 일대다 다형성 관계라고 한다. 다형성 관계에 익숙하지 않은 사람들은 매뉴얼을 참고하자.
key
는 모델의 어떤 값이 변경되었는지를 저장하는 값이다. 예를 들어 Post
모델의 title
이 수정되었다면 key
에 ‘title’이 값으로 저장된다.
카페용 애플리케이션에서 사물함은 Locker
모델로 관리했다. Locker
모델에 RevisionableTrait
를 끼워넣는다.
<?php namespace App; use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; use Venturecraft\Revisionable\RevisionableTrait; class Locker extends Model { use RevisionableTrait; ...이하 생략
Locker
를 업데이트하는 LockerService::update()
는 원래 아래와 같았다.
class LockerService { public function update($request, $locker) { DB::transaction(function () use ($request, $locker) { $this->backup($locker); $inputs = $this->buildInputsForUpdate($request, $locker); $locker->update($inputs); }); }
코드는 간단하다. 백업하고 업데이트할 데이터를 준비해서 업데이트한다.
이제 Revisionable이 수정내역을 관리해주기 때문에 백업하는 코드는 더이상 필요 없다. 그래서 $this->backup($locker);
줄을 지우고 LockerService::backup()
메소드를 통째로 제거했다.
테스트로 데이터를 하나 변경해봤다. 홍길동이 대여하고 있던 사물함을 장길산이 사용하게 된 시나리오다.
‘사용자’, ‘이용 시작 시점’, ‘이용 종료 시점’, ‘비밀번호’가 바뀌었다. revisions 테이블을 보면 아래와 같이 4개의 데이터가 추가되었다.
변경 행위는 한 번인데, 변경된 필드마다 데이터가 하나씩 생성되는 점은 조금 아쉽다. 현재로서는 같은 행동이 원인이 되어 바뀌었는지 여부를 판단할 수 있는 유일한 방법은 revisions
테이블의 created_at
이 같은지 확인하는 방법 뿐이다. 수정내역을 사용자의 행동 단위로 묶을 수 있도록 식별값을 하나 더 추가했으면 어땟을까 싶다.
기본적인 기능 외에 아래와 같이 다양한 옵션을 제공한다. 필요할 때 패키지의 매뉴얼을 보고 활용하자.
- 변경 뿐만 아니라 생성 기록도 저장하기
- 저장할 필드, 혹은 제외할 필드 지정하기
- 수정내역 저장 비활성화
- 수정내역 최대 저장 갯수 지정
- 출력 형식 변경하기
이 글은 7월 2일자 1일 1식 라라벨에 발행된 글입니다. 8월호 구독자를 모집하고 있습니다. 월 1만원으로 최신 라라벨 소식을 받아보세요.