본문 바로가기

프로그래밍(TA, AA)/자바스크립트

[자바스크립트] ExtJS MVC 아키텍처

대형 웹 클라이언트 애플리케이션은 항상 작성, 조직화 그리고 유지보수에도 어려움을 겪게됩니다. 대형 웹 애플케이션은 더많은 기능과 개발자들이 추가될때 순식간에 컨트롤할 수 있는 범위를 벗어나기도 합니다. 새로운 어프리케이션 아키텍처를 포함한 Ext JS 4는 코드를 조직화할 수 있을 뿐만 아니라 프로그래머의 작성 코드량도 감소 시켜줍니다.


ExtJS4의 애플리케이션 아키텍처는 Model과 Controller를 포함한 MVC 패턴을 따릅니다. 그것은 이미 존재하는 많은 MVC 아키텍처들과는 약간 다른 형태를 보입니다.


  • Model은 필드와 데이터에 대한 콜렉션입니다. 예를 들면 User Model은 username과 password를 포함하고 있을 것입니다. Model는 데이터 패키지를 통해 자기 자신 객체를 어떻게 보존해야하는지를 알고 있습니다. 그리고 associations 기법을 통하여 다른 model들과 연결될 수도 있습니다. ExtJS4 모델은 또한 ExtJS3 레코드 클래스와 거의 비슷하게 동작합니다. 그리고 일반적으로 데이터를 그리드와같은 다른 컴포넌트에 보여주기위해 Store와 함께 사용됩니다.
  • View는 컴포넌트의 모든 타입을 일컫습니다. (grids, trees, panel 등이 모두 view입니다)
  • Controllers는 당신의 앱을 동작시키는 코드의 대부분을 저장되는 특별한 공간입니다. view를 렌더링하거나, Model을 초기화하고 혹은 다른 app 로직처리 등이 포함됩니다.

아래는 User Data를 다루는 아주 간단한 애플리케이션에 대한 가이드입니다. 이를 통해 우리는 Ext JS 4 애플리케이션을 사용하여 애플리케이션을 조합하는 방법에 대해 알아볼 것입니다.

이 애플리케이션 아키텍처는 실제 클래스와 프래임워크와 비슷한 구조와 일관성을 제공합니다. 이 코딩 컨벤션은 아래와 같은 중요한 이점을 얻을 수 있습니다.

  • 모든 애플리케이션은 아래 방식과 같게 동작될 것입니다.
  • 모든 동작 방식이 동일하기에, 앱들 사이의 코드를 공유하는 것도 쉽게 됩니다.
  • 당신은 제품 사용성을 위해 당신의 어플리케이션을 최적화된 버전으로 만들수있는 빌드 툴도 사용할 수 있을 것입니다.

파일 구조


Ext JS 4 애플리케이션은 모든 앱을 위해서 동일하게 조직화된 파일구조를 따릅니다. 기본레이아웃에 따라 ExtJS 4를 생성하는 가이드는 여기를 참조합니다. MVC 레이아웃에서, 모든 클래스들은 app 폴더 안에 위치되어 있습니다. 그리고 그것은 당신의 models, views, controllers, stores 네임스페이스로 서브폴더를 가질 수도 있습니다. 아래는 폴더 구조를 구성하는 간단한 예제 입니다.



우리는 전체 애플리케이션을 'account_manager'라 불리우는 하나의 폴더 안에 전체 프로젝트를 캡슐화할 수 있게도 합니다. Ext JS 4 SDK의 필수 파일은 ext-4 폴더 안에 포장되어 있습니다. 따라서 index.html에서의 내용을 아래와 같이 됩니다.


<html>
<head>
    <title>Account Manager</title>

    <link rel="stylesheet" type="text/css" href="ext-4/resources/css/ext-all.css">

    <script type="text/javascript" src="ext-4/ext-debug.js"></script>

    <script type="text/javascript" src="app.js"></script>
</head>
<body></body>
</html>



app.js 안에 애플리케이션 만들기


모든 Ext JS 4 애플리케이션은 Application 클래스 인스턴스에서 구동됩니다. Application에는 당신의 애플리케이션에 위한 전역적 세팅(앱 이름 등등) 뿐만 아니라 앱에서 사용되는 모든 mode, view, controller에 대한 참조를 포함합니다. Application은 또한 launch 함수(모든 것이 load 되었을 때 자동적으로 시작)도 제공합니다.


그럼 간단하게 사용자 계정을 관리하는 것을 돕는 계정관리 앱을 만들어보겠습니다. 먼저 애플리케이션을 위한 전역 네임스페이스가 필요합니다. 모든 ExtJS4 애플리케이션은 하나의 Single global 변수를 갖고 있어야 합니다. 그 안에는 애플리케이션의 클래스들에 대한 참조가 존재합니다. 통상적으로 우리는 짧은 전역 변수를 선호하는 이유로 예제의 앱이름은 AM입니다.

Ext.application({
	requires: ['Ext.container.Viewport'],
	name: 'AM',
	appFolder: 'app',
	launch: function() {
		Ext.create('Ext.container.Viewport', {
			layout: 'fit',
			items: [
				{
					xtype: 'panel',
					title: 'Users',
					html: 'List of users will go here'
				}
			]
		});
	}
});

먼저, Application 클래스를 생성하기 위해 Ext.application을 실행시키며 AM이라는 앱이름을 넘겨줍니다. 그리고 자동적으로 AM이라는 전역 변수가 세팅이 되고, Ext.Loader에 네임스페이스가 등록됩니다. 이는 app 객체 내 appFolder 설정 옵션을 app으로 설정시킵니다. 우리는 또한 제공합니다. 간단한 실행 함수를 간단한 판넬을 포함하는 Viewport를 생성하는 작성하였고, 결과는 아래와 같습니다.




Controller 정의 하기


Controller는 애플리케이션을 바인딩하는 접착제와 같은 역할을 합니다. Controller 객체들은 이벤트들에 대한 listener 역할(일반적으로 뷰에서 발생하는 이벤트에 대한)과 몇몇의 action을 수행합니다. Account Manager 애플리케이션에서 controller를 작성해 보겠습니다. 아래 코드를 app/controller/Users.js 경로 안에 추가 합니다.

Ext.define('AM.controller.Users', {
	extend: 'Ext.app.Controller',
	init: function() {
		console.log('Initialized Users! This happens before the Application launch function is called');
	}
});


그리고 나서 app.js 안 application config에 새롭게 생성한 User Controller를 추가합니다.

Ext.application({
	...
	controller: [
		'Users'
	],
	...
});


브라우저 안에 index.html를 방문함으로써 애플리케이션이 로드됩니다. User 컨트롤러는 자동적으로 로드됩니다. (왜냐하면 우리가 위의 소스코드처럼 Application 정의 영역에 이를 명시하였기 때문입니다), 그리고 이것의 초기화 함수는 Application launch 함수 이전에 호출됩니다.


Controller의 init 함수는 view와 함께 상호작용하는 controller를 세팅하기에 가장 적절한 장소입니다. 그리고 이것은 보통 또다른 Controller 함수 컨트롤과 연결되어 사용되어 지기도 합니다. 이 control 함수는 view 클래스로 이벤트로부터 반응을 쉽게 함게 합니다. 그리고 handler 함수와 함께 일부 action을 수행하기도 합니다. 패널이 렌더링될 때, 알림을 수행하도록 User Controller를 업데이트 해보겠습니다.


Ext.define('AM.controller.Users', {
	extend: 'Ext.app.Controller',
	init: function() {
		this.control({
			'viewport > panel': {
				render: this.onPanelRendered
			}
		});
	},
	onPanelRendered: function() {
		console.log('The panel was rendered');
	}
});

application의 view에 대한 listener 동작을 세팅 용도로 this.controler을 사용하기 위해 init 함수를 업데이트 하겠습니다. controller 함수는 페이지에 컴포넌트에 대한 참조를 쉽고 빠르게 얻기 위해서 새로운 ComponentQuery 엔진을 사용합니다. 간단히 이것을 통해, 페이지의 컴포넌트를 매칭시켜 쉽게 찾을 수 있게 CSS와 같은 selector를 사용합니다.


위에 명시된 init 함수 안에 'viewport > panel'는 Viewport의 child 객체 중 모든 Panel 객체를 찾겠다는 의미입니다. 그리고 나서 이벤트 이름과 해당 handler 함수를 매핑하여 오브젝트에 제공합니다. 해당 selector에서 render 이벤트에 대해서 onPanelRendered 함수만 매핑시켜주면 모든 이벤트에서 같은 listener 사용도 가능합니다.


이는 조직화된 코드를 구성하는것이 얼마나 쉬운지를 보여주는 한 샘플입니다. 그럼 앞으로 grid를 추가하여 app을 조금더 구체화 시켜보겠습니다.



View 정의 하기


지금까지 우리는 Application 내 app.js와 app/controller/User.js 파일에 아주 적은 줄만 추가해보았는데요. 이제 우리 시스템에 모든 사용자를 모여주는 grid를 추가함으로써 애플리케이션의 로직을 조금더 보기좋게 조직화하고 뷰를 사용하는 것에 대해 시작해보겠습니다.


뷰는 Component로, 대개 Ext JS 컴포넌트의 서브클래스로 정의되어 있습니다. app/view/user/List.js 경로내 새로운 파일을 생성하고 아래의 소스코드를 추가하는 방식으로 User grid를 생성할 수 있습니다.


Ext.define('AM.view.user.List', {
	extend: 'Ext.grid.Panel',
	alias: 'widget.userlist',

	title: 'All Users',

	initComponent: function() {
		this.store = {
			fields: ['name', 'email'],
			data: [
				{name: 'Ed', 	email: 'ed@sencha.com'},
				{name" 'Tommy', email: 'tommy@sencha.com'}
			]
		};

		this.columns = {
			{header: 'Name',   dataIndex: 'name',  flex: 1},
			{header: 'Email',  dataIndex: 'email', flex: 1}
		};

		this.callParent(arguments);
	}
});

View 클래스는 일반 클래스입니다. 우리가 Grid Component를 확장하고, alias를 지정하려고 한다면 xtype를 이용하면 됩니다. grid를 렌더링할 컬럼값과 store 설정을 넘겨줍니다.


다음 우리는 Users 컨트롤러에 해당 view를 추가합니다.우리는 'widget.'이라는 특별한 포맷을 사용하여 alias를 지정할 것이기 때문에, 우리는 지금은 userlist라고 xypte을 지정합니다.


Ext.define('AM.controller.Users', {
	extend: 'Ext.app.Controller',
	views: [
		'user.List'
	],
	init: ...
	onPanelRendered: ...
});


그러고 나서 main viewport 안에 그것을 렌더링하기 위해 app.js를 수정합니다.

Ext.application({
	...
	launch: function() {
		Ext.create('Ext.container.Viewport', {
			layout: 'fit',
			items: {
				xtype: 'userlist'
			}
		});
	}
});

views array 안에 user.List를 정의해야 한다는 것을 유의합니다. 이를 통해 뷰파일이 자동적으로 애플리케이션으로 로딩되고, 우리가 앱을 실행할 때 이를 사용할 수 있게 됩니다. 애플리케이션은 서버로 부터 자동적으로 파일을 불러오는 Ext JS4의 새로운 동적 로딩 시스템을 사용합니다. 



grid 제어 하기


onPanelRendered 함수를 주목해보겠습니다. grid 클래스를 'viewport > panel'로 매치시키고 있습니다. Grid를 확장한 클래스 역시 Panel 클래스입니다. 우리가 추가한 listener는 사실 viewport의 모든 Panel 혹은 Panel의 서브 클래스에 추가됩니다. 그래서 우리는 새로운 xype을 사용하여 범위를 좁혀보도록 하겠습니다. 그럼 grid의 각 row를 더블클릭하는 것에 대한 listener로 대체해보겠습니다.


Ext.define('AM.controller.Users', {
	extend: 'Ext.app.Controller',
	veiw: [
		'user.List'
	],
	init: function() {
		this.control({
			'userlist': {
				itemdblclick: this.editUser
			}
		});
	},
	editUser: function(grid, record) {
		console.log('Double clicked on ' + record.get('name'));
	}
});

ComponentQuery selector가 간단히 'userlist'로 변경되었으며, 이벤트 이름은 itemdblclick으로 핸들러 함수는 editUser로 변경되었습니다. 



console에 로깅하는 것도 좋지만, 우리는 사용자 정보를 실제로 수정해보려 합니다. 그럼 'app/view/user/Edit.js' 파일 안에 새로운 뷰를 작성해 보겠습니다.


Ext.define('AM.view.user.Edit', {
	extend: 'Ext.window.Window',
	alias: 'widget.useredit',

	title: 'Edit User',
	layout: 'fit',
	autoShow: true,

	initComponent: function() {
		this.items = [
			{
				xtype: 'form',
				items: [
					{
						xtype: 'textfield',
						name: 'name',
						fieldLabel: 'Name'
					},
					{
						xtype: 'textfield',
						name: 'email',
						fieldLabel: 'Email'	
					}
				]
			}
		];

		this.buttons = [
			{
				text: 'Save',
				action: 'save'
			},
			{
				text: 'Cancel',
				scope: this,
				handler: this.close
			}
		];

		this.callParent(arguments);
	}
});

다시 우리는 존재하는 컴포넌트의 서브클래스를 정의합니다 - Ext.window.Window. 우리는 itmes와 buttons를 정의하기 위해 initComponent를 사용합니다. 우리는 또한 single itme으로 fit 레이아웃을 사용합니다. 그것은 이름과 이메일을 수정할 수 있는 필드를 포함합니다. 그리고 window를 닫는 버튼과 변화를 변경하는 저장 버튼 두개를 만들었습니다.


우리가 지금 만든 모든것은 컨트롤러에 View로 추가합니다.

Ext.define('AM.controller.Users', {
	extend: 'Ext.app.Controller',
	views: [
		'user.List',
		'user.Edit'
	],
	init: ...
	editUser: function(grid, record) {
		var view = Ext.widget('useredit');

		view.down('form').loadRecord(record);
	}
});

먼저 우리는 Ext.widget이라는 간편한 메소드를 사용하여 뷰를 생성합니다. 이건 Ext.create('widget.useredit')와 동일합니다. 그리고 나서 우리는 windows 폼 참조를 빠르게 얻기 위한 용도로 ComponentQuery를 활용합니다. ExtJS4의 모든 컴포넌트는 down 함수를 가지고 있습니다. 그것은 어떤 child 컴포넌트라도 빠르게 찾을 수 있도록 하는 ComponentQuery 셀렉터 기능입니다.


grid의 row를 더블클릭하면 아래와 같은 일이 벌어지게 됩니다.



Model과 Store 생성하기


사용자의 정보를 수정하고 저장하기 위한 준비가 거의 끝나갑니다. 우리는 우리의 코드를 약간 리팩토링할 필요가 있습니다.


AM.veiw.user.List 컴포넌트가 안에 Store를 만들었다면 이번에는 우리는 바깥의 Store를 참조하는 방식을 구현해보겠습니다. 이번에는 app/store/Users.js 안에 Store 객체를 생성해 보겠습니다.

Ext.define('AM.store.Users', {
	extend: 'Ext.data.Store',
	fields: ['name', 'email'],
	data: [
		{name: 'Ed', 	email: 'ed@sencha.com'},
		{name: 'Tommy',	email: 'tommy@sencha.com'}
	]
});


이제 우리는 2가지 작은 변화를 줄것입니다. 먼저 컨트롤러가 로드될때 store를 활용할 수 있도록 Users 컨트롤러에 해당 store을 추가합니다.

Ext.define('AM.controller.Users', {
	extend: 'Ext.app.Controller',
	stores: [
		'Users'
	],
	...
});


그러고 나서 우리는 app/view/user/List.js id로 Store를 간단하게 참조할 수 있도록 수정합니다.

Ext.define('AM.veiw.user.List', {
	extend: 'Ext.grid.Panel',
	alias: 'widget.userlist',
	title: 'All Users',

	// we no loger define the Users store in the 'initComponent' method
	store: 'Users',

	initComponent: function() {
		this. columns = [
		...
	}
});

Users Controller에 정의한 stores를 포함시킴으로써 그들은 자동적으로 페이지에 로딩되어 집니다. 그리고 storeId를 통해 View안에서 참조를 더 쉽게 하도록 합니다.


store 안에 필드('name'과 'email')를 정의하는 순간, 충분히 잘 동작합니다. 하지만 Ext JS 4안에서는 Ext.data.Model 클래스가 있고, 그것을 활용하는 것으로 우리에게 User 정보 수정에 있어 여러 이점을 가져다 줍니다. Model을 활용하여 Store 객체를 생성하는 것으로 이번 섹션을 마무리하겠습니다. app/model/User.js 파일에 아래와 같이 코드를 추가합니다.


Ext.define('', {
	extend: 'Ext.data.Model',
	fields: ['name', 'email']
});

위 코드로 Model의 정의는 마무리됩니다. 이제 우리는 User Store 파일에 기존 필드 객체를 Model 명으로 대체합니다.


Ext.define('AM.store.Users', {
	extend: 'Ext.data.Store',
	model: 'AM.model.User',
	data: [
		{name: 'Ed', 	Email: 'ed@sencha.com'},
		{name: 'Tommy',	Email: 'tommy@sencha.com'}
	]
});

그리고 우리는 Users 컨트롤러에 User model에 대한 참조를 추가해줍니다.


Ext.define('AM.controller.Users', {
	extend: 'Ext.app.Controller',
	store: ['Users'],
	models: ['User'],
	...
});

리팩토링은 다음 섹션을 보다 쉽게 수행할 수 있게 하지만 애플리케이션의 현재 행동에 영향을 주는 일은 없어야 합니다. 만약 우리가 페이지를 리로드하고, 한 행을 더블 클릭한다면, 예상대로 User Window 창이 뜨는 것을 알 수 있습니다. 이제 기능적인 수정을 마무리해보겠습니다.




Model을 이용해 데이터 저장하기


데이터가 로딩된 users grid를 가지고 있기때문에 팝업창에서 수정 액션이 일어날때 gird 내용에 대한 변화가 일어나야 합니다. 사용자 수정 팝업은 위의 form을 포함해, 저장버튼을 정의하고 있습니다. 먼저 우리 컨트롤러의 save 버튼 동작이 일어날 때 litsener 용도로 init 함수를 수정해보겠습니다.


Ext.define('AM.controller.Users', {
...
init: function() {
this.control({
'viewport > userlist': {
itemdblclick: this.editUser
},
'useredit button[action=save]': {
click: this.updateUser
}
});
},
...
updateUser: function(button) {
console.log('clicked the Save button');
}
...
});

두번째 this.control에서의 호출을 위하여 ComponentQuery 셀렉터를 추가해보겠습니다. 'useredit button[action=save]'  이는 첫번째 만들었던 것과 같은 방식으로 동작합니다. 이건 User 수정 팝업 창에 자체에 'useredit'이라는 xtype을 정의합니다. 그리고 나서 저장 버튼에 save라는 액션명을 추가합니다. 이렇게 버튼에 대한 타겟을 정할 수 있습니다. 저장 버튼에 대한 핸들러로 updateUser 함수를 사용해보겠습니다.


저장버튼 클릭이벤트가 정확하게 핸들러에 매핑되도록 updateUser 함수에 실제 로직을 작성해보겠습니다. 이 함수 안에서 우리는 폼 바깥에서 데이터를 얻을 수 있고, 저장 액션이 수행되었을 때 User Store에서 변경된 정보가 반영됩니다. 그럼 우리가 어떻게 해야하는지 한번 살펴보겠습니다.


updateUser: function(button) {
	var win 	= button.up('window'),
	    form	= win.down('form'),
	    record	= form.getRecord(),
	    values 	= form.getValues();

	record.set(values);
	win.close();
}

click 이벤트는 사용자 click 여부에 대한 참조를 제공합니다. 하지만 여기서 가장 필요한것은 form안의 데이터에 접근하는 것입니다. 이번에도 ComponentQuery를 사용한다면 쉽게 이를 수행할 수 있습니다. 먼저 우리는 button.up('window') 명령어를 통해 사용자 수정 창에 대한 참조를 얻을 수 있습니다. 그러고 나서 win.down('form') 명령어를 통해 팝업창에서 form을 읽어들입니다.


위와 같은 방식으로 form안의 데이터를 간단히 record를 가져오면서 창이 닫아지게 됩니다. 받아온 정보로 grid 행을 업데이트 하는 방식으로 사용자 변경이 일어난 것을 알 수 있습니다.



서버에 저장하기


그럼 이제 마지막으로 서버사이드와 상호 작용하는 것에 대해 알아보겠습니다. 현재 User Store 안에는 두개의 사용자 record가 하드코딩 되어있습니다. 이번엔 이를 AJAX로 대신해보겠습니다.

Ext.define('AM.store.Users', {
	extend: 'Ext.data.Store',
	model: 'AM.model.User',
	autoLoad: ture,
	proxy: {
		type: 'ajax',
		url: 'data/users.json',
		reader: {
			type: 'json',
			root: 'users',
			successProperty: 'success'
		}
	}
});

data 속성을 삭제하고 그것을 Proxy로 대체했습니다. Proxy들은 ExtJS4에서 Store나 Model로부터 데이터를 로드하고 저장하는 방법을 수행합니다. AJAX, JSON-P, HTML5 로컬스토리지를 위한 Proxy들이 존재합니다. 여기서 우리는 간단한 AJAX 프록시를 사용했습니다. 'data/users.json' 경로로부터 데이터를 로드합니다.


Proxy에는 Reader가 존재합니다. reader는 Store를 이해할 수 있는 형식으로 서버 데이터를 디코딩하는 역할을 수행합니다. 이번에는 우리는 JSON Reader를 사용하여 root와 successProperty 설정을 구제화해보겠습니다. 그럼 이제 data/users.json 파일에 이전에 사용한 데이터 정보를 입력해보겠습니다.

{
	"success": true,
	"users" : [
		{"id":1, "name":'Ed',	 "email": "ed@sencha.com"},
		{"id":2, "name":'Tommy', "email": "tommy@sencha.com"}
	]
}

우리가 생성한 Store에서는 autoLoad 값만 true로 변경해 줍니다. 그것은 Proxy를 통해 데이터가 로드되었을데 이를 즉시 Store에 반영한다는 것을 의미합니다. 만약 우리가 지금 페이지를 refresh 한다면, 애플리케이션 안에 더이상 하드코딩할 필요 없이 이전에 같은 결과를 볼 수 있을 것입니다. 


마지막으로 해야할것은 변경된 값을 서버로 보내는 기능입니다. 이번 예제에서는 정적인 JSON 파일을 사용합니다. 그래서 우리는 데이터베이스의 변화를 확인하지는 못합니다. 하지만 우리는 최소한 모든것이 정확하게 조합되었는지를 확인할 수가 있게 됩니다. 먼저 우리는 다른 url로 변경된 정보를 보내기 위해 새로운 proxy를 사용하도록 변경해보겠습니다.

proxy: {
	type: 'ajax',
	api: {
		read: 'data/users.json',
		update: 'data/updateUsers.json'
	},
	reader: {
		type: 'json',
		root: 'users',
		successProperty: 'success'
	}
}

우리는 여전히 users.json으로부터 데이터를 불러읽어들이지만, 업데이트 작업의 경우는 updateUsers.json 파일로 보내집니다. 이건 우리 test data에 대한 덮어쓰기없이 작업이 수행된다는 것을 의미합니다. record를 업데이트 한 뒤에, updateUsers.json 파일에는 단순히 {"success": true} 데이터만을 포함하게 됩니다. 이것은 HTTP POST 커맨드를 통해 수행이 되기 때문에 당신이 아마도 404 에러를 피하기 위해서는 빈파일을 가지고 있어야 할 것입니다.


우리에게 필요한 유일한 다른 변화는 수정이후 그것에 대한 sync를 맞춰주는 것입니다. 이를 위해우리는 updateUser 함수 안에 몇 라인을 더 추가해보겠습니다.


updateUser: function(button) {
	var win 	= button.up('window'),
	    form	= win.down('form'),
	    record	= form.getRecord(),
	    values 	= form.getValues();

	record.set(values);
	win.close();
	// synchronize the store after editing the record
	this.getUsersStore().sync();
}

이로써 전체 샘플 예제 구현이 마무리되었습니다. 우리가 row를 수정 후, Save 버튼을 누르면 응답이 올바르게 되었는지는 updateUser.json 파일 안에 기록이 될 것입니다.


배치


새롭게 소개된 Sencha SDK 툴을 통해 우리는 어느때보다 더 쉽게 ExtJS4 애플리케이션을 배치할 수 있습니다. 이 도구를 통해 당신은 JSB3(JSBuilder 파일 포맷)의 형식으로 의존관계 매니페이스 파일을 생성할 수 있습니다. 그리고 당신의 애플리케이션을 수분안에 간단히 빌드할 수 있습니다.


이에 관한 내용은 여기를 참고하세요.



정리


여기까지 우리는 사용자 정보를 관리하고 서버로부터 수정정보를 돌려받는 간단한 애플리케이션을 작성했습니다. 우리는 우리의 코드를 좀더 명확하고, 조직화될 수 있도록 간단하고, 점진적으로 리팩토릭 했습니다. 주목할만한 것은 우리가 스파게티 코드를 생성하지 않고도 우리의 애플리케이션에 더많은 기능을 쉽게 추가할 수 있다는 점입니다.