В рамках развития информационной системы управления салоном красоты «МойСалон», уже около 10 лет успешно существующей и внедренной в десятки салонов России, его автором была выдвинута идея создания web-версии программы на базе одного из AJAX-фреймворков. Преследуя исследовательские и познавательные интересы, я занялся созданием макета и для начала разработал приблизительный проект прототипа, максимально независимый от конкретной библиотеки. В качестве двух возможных библиотек были выбраны Vaadin и SmartGWT. Развитие прототипа продолжилось с последней, поскольку именно она обеспечивает наибольшее кеширование страниц на стороне пользователя, максимально разгружая сервер от частых запросов.
Ядром исходной информационной системы является приложение на C++/MFC. Начальной задачей прототипирования было выбрано создание аналога журнала клиентов, одного из нескольких доступных пользователю журналов. Интерфейс журнала клиентов представлен ниже:

Очевидно, что основной элемент интерфейса – это список клиентов, который можно редактировать, а наиболее сложный элемент – это множество форм, доступных через различные закладки. Стоит отметить, что остальные журналы в той или иной степени следуют этому шаблону интерфейса.
В основу разработки был положен шаблон MVP «Model-View-Presenter», представляющий собой усовершенствованный вариант Model-View-Controller, в котором Model избавлена от функций хранения состояния интерфейса, а также может не участвовать в качестве обязательного посредника View-Controller(Presenter).
Создание объектов Model, View, Presenter было основано на шаблонах Factory Method и Abstract Factory, как показано на диаграммах ниже.

Иерархия классов для View (JournalAppGWT и EmployeeJournalApp – начальные классы для SmartGWT и Vaadin, соответственно):

Аналогичный подход использовался для создания объекта-модели. Инициализация модели, представления и объекта Presenter в коде GWT выглядит так:

//Создать подходящую модель
IModelFactory modelFactory = AbstractModelFactory.createModelFactory(AppTypes.getCurrentImpl());
IModel model = modelFactory.createModel(AppTypes.getCurrentType());

//Создать главное View
IViewFactory viewFactory = AbstractViewFactory.createViewFactory(AppTypes.getCurrentImpl());
IJournalView mainView = viewFactory.createMainView(AppTypes.getCurrentType(), model);

//Создать главный Presenter
JournalAppPresenter journalPresenter = new JournalAppPresenter(mainView, model);

//Поместить интерфейс на страницу
RootPanel.get("journalContainer").add((Widget)mainView.asWidget());

В данном коде учитывается разделение объектов по типу журнала (AppTypes.getCurrentType()) и по типу реализации интерфейса, то есть используемой AJAX-библиотеки (AppTypes.getCurrentImpl()). Конкретные объекты модели и представления размещены в соответствующих пакетах, например (SG = Smart Gwt):
com.mysaloon.ui.smartgwt.client.model.EmployeeModelSG
com.mysaloon.ui.smartgwt.client.view.EmployeeJournalViewSG

Абстрактные интерфейсы для model и view:
com.mysaloon.ui.core.view.IJournalView<T>
com.mysaloon.ui.core.model.IModel<T>
- параметризованы типом сущностей, отображаемых в основном списке журнала.

За обработку событий отвечает Presenter. Он реализует интерфейс EventHandler и посылает запросы на обновление View при обработке соответствующих событий. Например:

private void registerForEvents() {
EventBus.getInstance().addHandler(this, DataChangeEvent.class);
//... подписка на другие типы событий ...
}

public void handleEvent(Event e) {
if (e instanceof DataChangeEvent) {
onDataChange((DataChangeEvent)e);
} else if ... ...

private void onDataChange(DataChangeEvent e) {
mainView.refreshListFromModel(selectedItemIndex);
itemsCount = model.getItemList().size();
}

Диаграмма для Presenter’а и связанных с ним классов:

Серверная часть приложения реализована для варианта SmartGWT при помощи средств GWT для создания сервлета, обрабатывающего клиентские запросы. Вот фрагмент клиентского кода:

public class ModelSG<T extends Persistent> implements IModel<T>{

protected JournalServiceAsync journalService = JournalServiceManager.getInstance();
List<T> itemList = new ArrayList<T>();
public void loadItemList() {
//Сгенерировать запрос к удаленному GWT-сервису
Command cmd = new           LoadListCommand(AppTypes.getCurrentType());
journalService.executeRequest(cmd, new    CommandResponseAdapter() {
public void onSuccess(CommandResponse result) {
itemList = new ArrayList<T>(
(Collection<T>)((CollectionResponseData)result.getResponse()).getCollection());
EventBus.getInstance().fireEvent(new DataChangeEvent());
}
});
}

И фрагмент серверного кода, обрабатывающий полученный инкапсулированный в объект-команду запрос:
public CommandResponse executeRequest(Command command)     throws IllegalArgumentException {
CommandResponse response = null;
EmployeeDAO employeeDao = new EmployeeDAO();
//Запрос загрузки списка
if (command instanceof LoadListCommand) {
LoadListCommand loadListCmd = (LoadListCommand)command;
switch (loadListCmd.getAppType()) {
case EMPLOYEE_JOURNAL:
List<Employee> employees = employeeDao.getEmployeeList();
response = new CommandResponse(command,
new CollectionResponseData(((Collection<Persistent>)
(new ArrayList<Persistent>(employees))
)));
break;
}
}
//Запрос возможности удаления записи
else if //... ... ...

EmployeeDAO реализовано при помощи Hibernate …