Spring Framework- projektowanie aplikacji w oparciu o koncepcję MDA

Chciałbym podzielić się swoimi doświadczeniami, które zdobyliśmy (ja i mój mały zespół) przy realizacji pewnego rozwiązania opartego o Oracle Portal (OC4J), OID(Oracle Internet Directory) i Spring (Portlet+Web) Framework.

Zadanie polegało na stworzeniu dwóch rzeczy:

  • aplikacji webowej, która pozwoli na zarządzanie danymi o pracownikach działów jednostki
  • sparametryzowany portlet, który możnaby było położyć na stronach poszczególnych działów i przy jego pomocy wyświetlać informacje o zatrudnionych w nim osobach.
Obydwie aplikacje były bardzo proste, spodziewaliśmy się szybkich efektów, więc ochoczo zabraliśmy się do pracy.

Bardzo szybko pojawił się problem - złożoność aplikacji wydawała się tak niewielka, że zaniedbaliśmy fazę projektowania, co zaowocowało tym, że wkrótce pogubiliśmy się we własnym kodzie. Do tego doszły problemy związane z obsługą portletów WSRP przez kontener OC4J - ogólnie wszystko złożyło się na ogólny chaos.

Złe widoki na przyszłość zmusiły nas do refleksji nad sposobami i narzędziami, które możemy wykorzystać do dobrego zaplanowania, a potem zarządzania kodem w naszym projekcie.

Wobec naszej aplikacji zastosowaliśmy rozwiązanie dosyć brutalne i dramatyczne - stworzyliśmy projekt a następnie napisaliśmy wszystko na nowo... ale cóż... według starego powiedzenia... jak ktoś nie ma w głowie...

Z naszych rozważań wyewoluowało pewne rozwiązanie, propozycja sposobu modelowania aplikacji webowych (szczególnie tych springowych ) które opiera się na koncepcji Model Driven Architecture ( nie mylić z DeadLine Driven Architecture ).

Jako narzędzie do modelowania wykorzystaliśmy potężne narzędzie CASE - Enterprise Architect ( jest to środowisko komercyjne, ale stosunkowo niedrogie - a przez to powszechnie dostępne).

W moich rozważaniach chciałbym skupić się nie na samej aplikacji, lecz na stosowanym przez nas sposobie projektowania. Jako przykłady wykorzystam modele stworzone przy realizacji innego, bardziej dowolnego rozwiązania.

Wielkie podziękowania kieruję w stronę BOJARA za dobry pomysł, wspólną pracę nad rozwiązaniem i wsparcie teoretyczne.

Wprowadzenie teoretyczne

Model Driven Architecture (MDA) - wprowadzenie


Opracowano na podstawie MDA Guide Version 1.0.1

Na początek nie da się uciec od definicji kilku podstawowych pojęć, które związane są koncepcją Model Driven Architecture.


Model Driven Architecture
jest koncepcją wspomagającą i porządkującą procesy tworzenia aplikacji i systemów komputerowych. Jej głównym celem jest dostarczenie mechanizmów i narzędzi, które pozwolą spojrzeć na system ze szczególnym naciskiem na jego przenośność, a więc niezależność od platformy systemowej która wykorzystana będzie do jego uruchomienia.

MDA zakłada tworzenie kilku głównych poziomów modeli, obejmujących różne aspekty projektowanego systemu:

Computation Independent Model (CIM) - model domeny - jest to zestaw modeli skupiających się nie na architekturze tworzonej aplikacji,lecz na środowisku, w którym ma ona działać. Modele te składają się na specyfikacja wymagań systemu.

Platform Independent Model (PIM) - są to modele pokazujące tą część architektury systemu która jest całkowicie uniezależniona od platformy pod której kontrolą będzie on działał.

Platform Specific Model (PSM) - zestaw modeli, które powstają po uzupełnieniu modeli PIM o informacje specyficzne dla platformy która będzie wykorzystywana do działania systemu.

Platform Model - jest to model pokazujący najistotniejsze ( z punktu widzenia działania tworzonej aplikacji ) fragmenty architektoniczne platformy, która wykorzystana zostanie do uruchomienia działającego systemu.

Koncepcja MDA szczególny nacisk kładzie na dynamiczne przejście pomiędzy modelami PIM i PSM. Dokonuje się ona w oparciu o proces tzw. transformacji modeli.
Polega on na tym, że model PIM jest uzupełniany o informacje dodatkowe, które determinowane są rodzajem wskazanej platformy docelowej. W wyniku połączenia tych danych otrzymywany jest model PSM.

Definiowanie zasad przekształcenia modelu (Mapping):

W wyniku mapowania określone zostają zasady transformacji , którym poddany ma zostać model PIM by powstał model PSM. Zasady mapowania są determinowane przez wybór platformy do jakiej przekształcony ma zostać model PIM.

Można wyróżnić trzy rodzaje mapowań
  • Model Type Mapping - mapowanie na poziomie metamodelu
  • Model Instance Mapping - mapowanie instancji modelu
  • Combined Type and Instance Mapping- mapowanie łączone
ad. 1: Model Type Mapping
Architekt tworzący model PIM używa typu elementu istniejącego w zadanym języku, a następnie przekształcenie odbywa się według określonych reguł i powstaje typ elementu istniejący w innym języku.

Należy zwrócić uwagę na fakt, że przekształcenie odnosi się nie do samego ELEMENTU, ale do jego TYPU.

Przykładem tego rodzaju mapowania jest przekształcenie Klasy w Encję.

Specjalnym rodzajem tego typu mapowań jest mapowanie metamodelu (Metamodel Mappings). W jego przypadku zarówno elementy modelu PIM jak i PSM są zdefiniowane w repozytorium MOF (Meta-Object Facility). Mapowanie tego typu powoduje przekształcenie typów obiektów znajdujących należących do jednego metamodelu (na podstawie którego określony jest model PIM) na typy elementów należących do innego (na podstawie którego stworzony został model PSM).

ad. 2: Model Instance Mapping
Jest to mapowanie na poziomie obiektów modelu. Jej integralną częścią jest oznaczenie elementów w modelu PIM. Przy wykonywaniu przekształcenia, następuje identyfikacja sposobu transformacji obiektów oznaczonych określonym znakiem, a następnie zastosowanie jej i przekształcenie ich do odpowiedniego elementu w modelu PSM.

Znaczniki nie są częścią modelu PIM. Są one w pewien sposób zależne od platformy.
Specyfikacja mówi:
"The marks can be thought of as being applied to a transparent layer placed over the model"

Istnieje wiele sposobów oznaczania modelu PIM - ciekawym rozwiązaniem wydaje się używanie profili UML. Stanowią one rozszerzenie języka UML. Pozwalają na zdefiniowanie nowego języka modelowania, opartego na UML-u. Ten nowo zdefiniowany język może posłużyć do tworzenia modeli, lub na zastosowanie go do istniejącego modelu. Zastosowany profil może zostać zdjęty z modelu, a wtedy model zachowuje te cechy, które posiadał przed zastosowaniem go. Każdy model który używa profili UML jest ciągle modelem UML.

ad. 2: Combined Type and Instance Mapping
Jest to sposób mapowania oparty na obydwu opisanych wcześniej.


Przekształcanie modeli (Transformation)

Transformacja modelu polega na przekształceniu oznaczonego modelu PIM w model PSM. Znaczniki modelu PIM wykorzystywane są do określenia wymaganego odpowiedniego przekształcenia.

Realizacja koncepcji

Profil UML - jako rozszerzenie metamodelu

Realizację zadania rozpoczniemy od stworzenia nowego języka modelowania opartego na UML.
Posłużymy się opisywanymi wcześniej Profilami UML. Wszystkie przykłady oparte będą na narzędziu Enterprise Architect.

Profil UML musimy traktować jako specjalizowane rozszerzenie UML-a przystosowane do używania w określonej domenie problemowej.
Dla każdego elementu naszego profilu musimy wskazać determinujący jego cechy typ bazowy, który istnieje w definicji języka UML.
Musimy tu odnieść się do struktury samego języka UML - a więc do jego metamodelu.
Ten generyczny tryb występujący w specyfikacji nazywa się metaklasą (Metaclass). Jest on podstawą do tworzenia każdego obiektu występującego w UML.

Cytując za Wiki:
"... a metaclass is a class whose instances are classes. Just as an ordinary class defines the behavior of certain objects, a metaclass defines the behavior of certain classes and their instances..."

Przykładowo wygląd, cechy i zachowanie każdego obiektu Class w UML-u, jest determinowane definicją jego metaklasy Class. Tą zasadę można zastosować dla każdego elementu języka UML.

Najprostszym sposobem rozszerzania metamodelu, powszechnie stosowanym przy tworzeniu Profili jest zastosowanie stereotypów. Przy tworzeniu profilu wskazujemy określoną metaklasę, której cechy chcemy rozszerzać, a następnie rozszerzamy ją określonym stereotypem. Dla tego nowego elementu możemy zdefiniować nowe cechy, wygląd i zachowanie. Taki nowy element może być położony na każdym modelu, do którego zostanie zaimportowany dany profil.

Należy zwrócić uwagę na pewną cechę rozszerzania metamodelu przy pomocy stereotypów. Rozszerzenie metaklasy Class stereotypem X, po zaimportowaniu takiego profilu do nowego modelu nie pozwoli nam na używanie obiektów typu X, lecz obiektów Class o stereotypie X. Nie jest tworzona nowa metaklasa - następuje tylko rozszerzanie już istniejącej. Poniżej znajduje się ilustracja opisywanego przypadku - na początek definicja profilu:


a następnie wynik uzyskany po zaimportowaniu danego profilu do nowego modelu i użyciu na nim elementu X.

Zgodnie z oczekiwaniami dodana została nowa klasa o stereotypie X oraz cechach (w tym przypadku zobrazowanych kolorem) zdefiniowanych w profilu dla elementu X.

Profil można traktować jako model modelu przystosowanego do specyficznej dziedziny problemowej.

Wsparcie oferowane przez narzędzie EA w zakresie zarządzania profilem

Program Enterprise Architect daje pełne wsparcie do tworzenia i obsługi profili UML. Pozwala na stworzenie modelu będącego profilem, a następnie wyeksportowanie go do pliku XML. Plik taki może bez problemu zostać zaimportowany do dowolnego modelu. Wtedy otrzymamy w nim nową belkę narzędziową, na której znajdą się elementy stworzone w profilu.

Okno służące do esportu danego profilu do pliku xml wygląda tak:

Bardzo użyteczną, oferowaną przez niego funkcją jest możliwość sortowania elementów. Można w dowolny sposób zarządzać kolejnością ich występowania w belce narzędziowej.

Wyeksportowany plik można zaimportować do dowolnego modelu:

Dodatkowo w menu kontekstowym uruchamianym prawym przyciskiem myszki na nazwie profilu (podświetlona na poprzednim ekranie) można wybrać opcję "Show Profile in UML Toolbox", co spowoduje umożliwieniem dostępu do nich przy pomocy palety narzędziowej:


Stworzenie profilu Spring w narzędziu Enterprise Architect

Chcielibyśmy, aby tworzony przez nas język modelowania wspierał nie tylko planowanie statycznej architektury aplikacji ( np. struktura klas ) , ale także pozwalał na zaplanowanie jej dynamiki (np. tworzenie map przepływów sterowania pomiędzy różnymi częściami aplikacji).

Pierwszym krokiem było stworzenie elementów występujących w warstwie webowej i interfejsu użytkownika.

Zdefiniowaliśmy stereotypy dla podstawowych klas używanych w warstwie webowej, a więc:
  • SimpleValidatorController
  • SimpleController
  • SimpleFormController
  • Widok JSP
Aby na diagramach przepływów sterowania i struktury od pierwszego spojrzenia wiedzieć z jakimi elementami ma się do czynienia - każdy stereotyp oznaczyliśmy odpowiednim kolorem.

Potem dwa elementy charakterystyczne dla warstwy biznesowej:

W warstwie biznesowej wyróżniliśmy dwa odrębne elementy:
  • BusinessInterface
  • SimpleBusinessClass

W celu łatwego rozróżnienia na diagramach będą one wyświetlane odpowiednim kolorem.

Warstwa danych:

W warstwie danych wyróżniono jeden wspólny interfejs - DAOInterface i kilka klas narzędziowych które wspomagają łączenie się z ldapem, lub z bazą danych za pośrednictwem Hibernate. Są to:
  • DAOHibernate
  • DAOLdap
  • LdapAttributeMapper
Dodatkowo, na potrzeby diagramów modelowania przepływu sterowania konieczne staje się zdefiniowanie kilku elementów, które będą oddawały dynamikę systemu, będą pokazywały sposób przekazywania komunikatów pomiędzy poszczególnymi elementami budowanej aplikacji. Elementy te będą łącznikami pomiędzy innymi elementami, a więc muszą być odmianą jakiegoś łącznika. Wybrano metaklasę "Dependency":

Utworzone elementy to:
  • Forward
  • Redirect
  • Request
  • Submit
Na wypadek gdyby trzeba było ręcznie dodać którychś z nich na przekształconym modelu zdefiniowano kilka elementów wchodzących w skład PSM:


Są to:
  • WebSimpleController
  • WebSimpleFormController
  • PortletSimpleController
  • PortletSimpleFormController
Kod tranformacji modelu PIM->PSM
Koncepcja MDA zakłada istnienie przekształcenia, które pozwoli na przetworzenie oznaczonego modelu PIM w model PSM, którego wygląd i struktura jest determinowana rodzajem wybranej technologii. Narzędzie EA pozwala na definiowanie szablonów takiego przekształcenia. Są one wykorzystane w procesie automatycznej translacji.
Poniżej zamieszczono fragmenty kod transformacji dla przykładowego elementu z profilu SpringSimpleController. Jest on napisany w wewnętrzny języku definiowania transformacji modeli narzędzia EA.

...

%TRANSFORM_REFERENCE("Class")%
%TRANSFORM_CURRENT("language","stereotype")%
language="Java"
stereotype="WebSimpleController"

Parent
{
name="Controller"
type="Implements"
}
Attribute{
name="logger"
type="Log"
default="LogFactory.getLog(getClass())"
scope="private"
}
Operation
{
name="handleRequest"
type="ModelAndView"
scope="public"
Parameter
{
name="request"
kind="in"
type="HttpServletRequest"
}
Parameter
{
name="response"
kind="in"
type="HttpServletResponse"
}
Tag
{
name="throws"
value="Exception"
}
}
...

Powyższa transformacja przypisana jest do modelu PIM, do elementu o stereotypie SimpleController. Jej dopasowanie i zastosowanie spowoduje stworzenie w modelu PSM klasy Java o stereotypie WebSimpleController. W kodzie tego szablonu można dopatrzeć się definicji właściwości(sekcja Attribute) i sparametryzowanych metod (sekcja Operation), które są determinowane przez implementację interfejsu Contoller (Sekcja Parent).

Generowanie kodu źródłowego

Nieodłącznym etapem tworzenia oprogramowania jest generowanie kodu. Powstaje on z diagramów PSM.

Poniżej zamieszczono fragment szablonu wykorzystywanego do generowania kodu źródłowego klas Java o stereotypie WebSimpleController zdefiniowany w wewnętrznym języku skryptów generujących kod EA.
...

import java.util.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

%ClassNotes%
%PI=" "%
%ClassDeclaration%
%ClassBody%
...

W sytuacji, gdy z modelu PSM generowany będzie kod aplikacji i nastąpi dopasowanie do klasy Java o stereotypie "WebSimpleController" to wykonany zostanie powyższy kod - spowoduje to automatyczne wygenerowanie zestawu importów przewidzianych dla tego elementu. Jest to warunkiem wygenerowania kodu projektu, który bez nanoszenia dodatkowych poprawek będzie gotowy do kompilacji.

Przykład

Mapa przepływu dla kilku przykładowych przypadków użycia:


Model PIM dla tego fragmentu aplikacji wygląda następująco:


Model PSM po transformacji Spring Portlet:


Model PSM po transformacji Spring Web:


Wygenerowane modele PSM mogą posłużyć do stworzenia plików źródłowych aplikacji:

Kod SimpleController po tranformacji Spring Web:

package net.beetle.web.projects;

import java.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
/**
* @version 1.0
* @created 09-kwi-2007 09:16:44
*/
public class ProjectsListController implements Controller {

private Log logger = LogFactory.getLog(getClass());

public ProjectsListController(){

}

public void finalize() throws Throwable {

}

/**
*
* @param request
* @param response
* @exception Exception
*/
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception{
return null;
}

}

Kod SimpleController po tranformacji Spring Portlet:

package net.beetle.web.projects;

import java.util.*;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.portlet.mvc.SimpleFormController;
import org.springframework.web.portlet.mvc.AbstractController;
import org.springframework.web.portlet.ModelAndView;
/**
* @version 1.0
* @created 09-kwi-2007 09:15:30
*/
public class ProjectsListController extends AbstractController {

private Log logger = LogFactory.getLog(getClass());

public ProjectsListController(){

}

public void finalize() throws Throwable {
super.finalize();
}

/**
*
* @param request
* @param response
* @exception Exception
*/
protected void handleActionRequestInternal(ActionRequest request, ActionResponse response)
throws Exception{

}

/**
*
* @param request
* @param response
* @exception Exception
*/
protected ModelAndView handleRenderRequestInternal(RenderRequest request, RenderResponse response)
throws Exception{
return null;
}

}


Aplikacja pokazująca sposób wykorzystania i zalety zaprezentowanego modelowania - gdy tylko czas pozwoli...

Brak komentarzy: