일요일, 8월 11

ubuntu_12_LTS] Redmine 설치 + git 연동 사용법


Redmine 설치 방법은 이 분 블로그를 따라함.
http://booiljoung.blogspot.kr/2013/01/ubuntu-1204-lts-redmine.html

*** Passenger 가 설치되지 않았을 때 ***
1, apt-get install libapache2-mod-passenger
2,a2enmod passenger
3,service apache2 restart

다만 설치를 따라하기 앞서
Mysql client 와 Server를 설치해야한다.

apt-get install mysql-server mysql-client

아니면 중간에 DB 때문에 설정을 물어봄.
하지만 DB를 먼저 설치하고 진행하면 mysql로 선택만 하면 끝~

Redmine과 apache 연동 설정이 끝나 웹으로 접속이 되면, (관리자 계정 admin:admin)
Git 연동을 시도할 차례.

먼저 git을 설치하자.


=== git  bare repository ==============

apt-get install git

( git을 설치하고나면 왼쪽 상단 관리 - 설정 - 저장소 에서 Git 체크박스가 활성화된다 )

git을 설치하면 아무 경로에나 git의 서버 repository 용 디렉토리를 만든다.
본인은 /git에 만듬

루트에 만들면 owner가 루트이기 때문에 바꿔줘야한다.
chown -R [user]:[group] git

이제 본격적으로
git upstream repository ( git 원격 저장소를 만든다. )

git init --bare test.git

test.git이 생성된 것을 확인할 수 있다.
로컬에서 해당 저장소를 테스트 해보도록 한다.

git clone /git/test.git

test 에 clone 이 됐다는 메시지가 보이면 성공이다.
이번에는 push를 해볼 차례.

cd test
touch README
git status

git status를 치면 메시지 중에 README가 보일 것이다.
README를 add 하고 커밋하고 푸시해보자.

git add README
git commit -m "test"

커밋이 문제없이 되면 push 한다.

git push

아마 push가 안될것이다.
왜냐면 branch를 정하지 않았기 때문인데,

git status

*master
만 달랑 출력될 것이다.

git push origin master

명령을 통해 branch를 설정하고, push가 되는 것을 확인했으면
다시 

git status

를 통해 branch를 확인한다. 



=== local git repository 와 remote 연동 ==================

이제 작업하던 소스를 git repository로 만들고 이를 원격 repo에 push 해보자.
프로젝트 루트로 가서 먼저 local git repo를 만든다.

git init

그리고 파일들을 추가한다.

git add *
git commit -m "test-commit"
git push

push 는 되지 않는게 정상이다. 원격 저장소를 추가해줘야하기 때문.

git remote add origin ssh://[레드마인서버주소]/git/test.git 

이렇게 원격 저장소를 추가한 후에도 push는 되지 않는데 
push를 하기 전에 먼저 pull을 해서 변경사항을 맞춰줘야하기 때문이다.

git pull origin master 

명령을 실행하면 원격저장소의 내용을 다운받아 변경사항을 맞춰주는데
우리는 처음하기 때문에 별 다르게 할 일은 없다.
이후 push하면 정상적으로 작업이 진행된다.

git push


=== redmine 프로젝트 설정 =============

레드마인에서는 프로젝트를 만든 뒤에 
설정- 저장소 에서 만들어둔 git 저장소를 추가하면 된다.

우리의 저장소 경로는
/git/test.git 

설정이 마무리 된 후 
프로젝트 - 저장소 로 가면 git 리포지터리의 내역들이 보이게 된다. 














금요일, 8월 9

최근 웹 개발환경 이해 - 진보된 웹 환경 공부 #3 MVC on Client-side & advanced



개요

 지난 글 진보된 웹 환경 공부 #2 에서는 REST를 쉽게 구현해 줄 수 있는 RESTlet Framework를 사용해 HelloWorld 서비스를 구현해 봤습니다. REST를 통해 서버를 구현하면서 가장 많이 바뀐 면은 Client-side 입니다.

 지난번에 보았듯이, REST를 구현한 서버는 URI를 통해 호출된 서버자원에 매칭되는 작업을 할 시에 ( 비지니스 로직, 디비, 이미지나 html에 대한 리소스 전송 ) 이를 클라이언트에 전송해주게 되는데, 결과를 JSP나 PHP등의 서버사이트스크립트 페이지를 통해 데이터가 넣어진 형태로 전송해주지 않고, JSON과 같은 데이터 형으로 보내주게 됩니다. 

 이와 같은 구현은, 전에 말씀드렸듯이 서버를 개발하는 사람이 stand-alone하게 개발할 수 있는 여건을 마련해줍니다. 내가 서버를 구현하는데 반환해줄 데이터를 View에 어떻게 넣어줄지 등에 대한 고민을 할 필요가 전혀 없어졌기 때문입니다. 하지만 동시에 Interface 설계가 중요해졌습니다. 

 클라이언트 측면에서는 전송받은 데이터를 View에 넣어줄 필요가 생겼기 때문에 javascript를 통해 서버로부터 데이터를 받아오고 받아온 데이터를 View에 넣어주는 라이브러리들이 등장하게 됩니다.

 이번 글에서는, REST가 구현된 RESTful 서버 구현의 마지막 단계인 클라이언트 측면에서 서버의 자원 호출을 통해 받은 데이터(JSON)을 통해 View를 구성 방법을 다루고자 합니다. 

또한, Javascript 라이브러리인 Backbone.js(MVC패턴), knockout.js(MVVM패턴) 두 가지를 동시에 다뤄서 MVC 패턴과 MVVM 의 차이점 (MVVM이 MVC 보다 뭐가 나은가?)에 대해서도 다루고자 합니다. 


본문

먼저 지난 번의 Helloworld 예제를 조금 수정해서 그럴듯한 사용자 정보가 나오는 REST 서비스를 만듭니다.

public class HelloWorldRESTResource extends ServerResource {
@Get("json")
    public 
    Representation HelloWorld(Representation entity) 
    {
JsonRepresentation response = null;
        try
        {
        LinkedHashMap<String, Object> list = new LinkedHashMap<String, Object>();
            list.put("user_name", "Hanbum Bak");
            list.put("email", "HanbumBak@whatyougonnado.com");
            response = new JsonRepresentation(list);
        } catch(Exception ex) {
        
        }
return response;
    }

}

이제 아래 주소로 접속하면 
http://localhost:8182/helloworld

요런 JSON이 날라옵니다 .
{"user_name":"Hanbum Bak","email":"HanbumBak@whatyougonnado.com"}


OK! 이제 JSON을 받아와 View를 만들어 볼 차례입니다.
연장자 우대해서 MVC 패턴을 사용한 Backbone을 먼저 사용해볼까요?

아! 먼저 RESTlet에서 일반 파일들을 서빙하도록 서비스를 등록해야 하는데, 이 글에서 다룰 범주는 아니므로 간단히 소스만 공개하고 넘어가겠습니다.


========== 파일서빙용 서버리소스 ============
public class StaticPathWebRoutable implements IRestletRoutable {
    
    @Override
    public Router getRestlet(Context context) {
    final String WEB_ROOT = 
"file:///"+
System.getProperty("user.dir") + 
System.getProperty("file.separator") +
"resources" +
System.getProperty("file.separator") +
"web";
    
        Router router = new Router(context);
        router.attach("", new Directory(context, WEB_ROOT));
        context.setClientDispatcher(new Client(context, Protocol.FILE));
        return router;
    }

    /**
     * Set the base path for the Topology
     */
    @Override
    public String basePath() {
        return "/";
    }

}

=========== 서비스 등록용 인터페이스 ==============

public class StaticWebRoutable implements IRestletRoutable {
    
/*
* classloader path = $(ProjectHome)/bin
*/
    @Override
    public Router getRestlet(Context context) {
        Router router = new Router(context);
        router.attach("", new Directory(context, "clap://classloader/web/"));
        context.setClientDispatcher(new Client(context, Protocol.CLAP));
        return router;
    }

    /**
     * Set the base path for the Topology
     */
    @Override
    public String basePath() {
        return "/";
    }
}

===== Router에 서비스 등록 =============
public class RootRouter extends Application {
protected List<IRestletRoutable> restlets;
@Override
    public Restlet createInboundRoot() {
Context context = getContext();
        Router baseRouter = new Router(context);
        baseRouter.setDefaultMatchingMode(Template.MODE_STARTS_WITH);
        
        /* TODO 
         * 나중에 모듈 로드 방식으로 고도화, 지금은 일단 하드코딩 
         */
        
        restlets = new ArrayList<IRestletRoutable>();
        restlets.add(new HelloWorldWebRoutable());
        restlets.add(new StaticPathWebRoutable());
        
        // attach 
        for (IRestletRoutable rr : restlets) {
            baseRouter.attach(rr.basePath(), rr.getRestlet(context));
        }
        
        return baseRouter;
    }
}


=== 그리고 HelloWorld 용 Routable도 추가합니다. =============

package com.swmaestro.sdn.HelloWorld;

import org.restlet.Context;
import org.restlet.routing.Router;

import com.swmaestro.sdn.core.IRestletRoutable;


public class HelloWorldWebRoutable implements IRestletRoutable {

@Override
    public Router getRestlet(Context context) {
        Router router = new Router(context);
        router.attach("/helloworld", HelloWorldRESTResource.class);
        return router;
    }

    /**
     * Set the base path for the Topology
     */
    @Override
    public String basePath() {
        return "/sm";
    }
}


여기까지 됐으면 본격적으로 Client 측면에서 아래의 목표 출력물을
Backbone과 Knockout 으로 각기 구현해보겠습니다.

목표 출력물
user-name : Hanbum Bak
email : HanbumBak@whatyougonnado.com


=== Backbone ================================

<필요한 라이브러리>
"https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"
"https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"
"https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"
</필요한 라이브러리>

HelloBackboneModel = Backbone.Model.extend({
url:"/sm/helloworld",
default: {
user_name: 'test',
email: 'test'
}
});

HelloBackboneView = Backbone.View.extend({
initialize: function(){
this.model.fetch({
success: function(data) {
HelloBackboneView.render();
}
});
},
render: function(){
var temp = "user-name : " + this.model.get("user_name");
temp += "<br>email : " + this.model.get("email");
        this.$el.html(temp);
    }
});

var HelloBackboneModel = new HelloBackboneModel();  
var HelloBackboneView = 

new HelloBackboneView({ model:HelloBackboneModel, el: $("#HelloBackbone") });


=== Knockout ================================

<필요한 라이브러리>
"http://cdnjs.cloudflare.com/ajax/libs/knockout/2.3.0/knockout-min.js"
"https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"
</필요한 라이브러리>


<p>userName: <strong data-bind="text: userName"></strong></p>
<p>email: <strong data-bind="text: email"></strong></p>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout/2.3.0/knockout-min.js"></script>

<script>
// This is a simple *viewmodel* - JavaScript that defines the data and behavior of your UI
function AppViewModel() {
var self = this;
this.userName = ko.observable();
    this.email = ko.observable();
    
    this.update = function() { 
    $.getJSON("/sm/helloworld", function(data) {
        self.userName(data.user_name);
        self.email(data.email);
    });
    };    
}

// Activates knockout.js
var temp = new AppViewModel();
ko.applyBindings(temp);
temp.update();
</script>


 MVC를 구현한 Backbone과 MVVM을 구현한 Knockout의 가장 큰 차이점은 눈에 보이다싶이 Data를 View에 Binding하는 형태입니다. Backbone 예제에서는 직접 html 을 입력해줬는데 실제로 Backbone에서 제공하는 Template기능을 사용하더라도 소스 코드만 다를뿐 기본적인 동작원리는 똑같이 html을 입력해주는 형태입니다.

 눈에 보이지 않는 차이점은 Observable로 표현되는 Observer에 따른 View의 갱신 방법인데... Backbone예제에는 이를 넣지 않았는데... 이유는 이 소스를 보면 기분이 아주 안좋아지기 때문입니다. (-_-; )

 MVC 에서는 View가 정말 아무 기능이 없습니다.
 그냥 model 을 사용자에게 보여주기 위한 수단일 뿐이죠.
 하지만 대부분의 언어(??)가 View를 통해 Event를 전달받을 수 있었습니다.
 제가 MVC를 이해한 순간부터 사람들이 왜 이 패턴을 사용하는지 의아했던 이유입니다.

 우리는 View와의 상호작용을 통해 Data를 편집합니다.
 만약 우리가 View의 Data를 수정했다면 처리 로직은 어떻게 되는 걸까요?

 MVC 에서는
 Controller가 입력장치의 Data를 받아 -> Model을 갱신하고 -> View에 반영

 MVVM 에서는
 View가 Data를 갱신하고 -> 데이터로 Model을 갱신할지를 프로그래밍.


 MVC패턴은 View에서 Event를 처리할 수 있게 된 이후부터는 비효율적인 패턴이며, View의 역할은 단순히 Model을 사용자에게 보여주는 것에서 사용자와의 상호작용을 처리하고 이를 통해 모델을 갱신할지 여부를 결정할 수 있는 선까지 확장되었습니다. 그리고 MVVM은 이와같이 바뀐 패러다임을 잘 적용했을 뿐 아니라, 이를 통해 View와 Model의 의존성을 보다 느슨하게 만든 GUI 구현 패턴입니다 


  

마치며

MVC is nothing. or everything.

http://tomyrhymond.wordpress.com/2011/09/16/mvc-mvp-and-mvvm/



MVC is nothing.
MVC 패턴은 예전에 제가 다룬 글에서 이야기했듯, 1970년대의 OS에서나 사용했던 GUI 구현 패턴이죠. 아무것도 아닙니다. 구시대의 잔재일 뿐이죠.

or everything.
하지만 동시에 MVC는 model 2로 발전하면서 Struts와 같은 Framework를 통해 개발자들에게 웹의 시대를 열어줬습니다. 발전된 MVC model 2 패턴은 Client와 Server 개발을 분리하고 Html 등을 담당하는 퍼블리셔 혹은 디자이너와 개발자에게 훌륭한 개발 환경을 제공해줬습니다.


 객체지향에서 100번을 강조해도 지나침이 없는 중요한 키워드는 바로 De-Coupling입니다. 이것은 쉽게 이야기해서, Server-Client 와 같이 긴밀하게 연결될 수 밖에 없다고 생각되는 경우에도 변경과 수정이 상호 간에 미치는 영향을 최소화 하는 것 입니다.

 이를 지켜야 하는 이유는 OOP에서 말하는 "단일 책임의 법칙" (SRP : Single Responsibility Principle)을 준수함을 통해 OOP의 장점인 "개발의 병렬화" 그리고 "재활용성" 을 얻을 수 있기 때문입니다. 보다 나은 "의존성 없는 협업체계"는 MVP를 거쳐 MVVM으로 발전되었습니다. MVVM으로 오면서 MVC model 2에서 해결할 수 없었던 부분들이 해결되어 ( GUI의 갱신 등에 대한... ) 전보다 더 원활한 협업이 가능할 것으로 기대됩니다.


-fin