SCAlenie (kompozyt) z Apache Tuscany i Apache Maven

Z Jacek Laskowski - Wiki Projektanta Java EE

Architektura komponentów usługowych (SCA - Service Component Architecture) jest, obok Service Data Object (SDO), głównym standardem umożliwiających realizację idei Architektury Zorientowanej na Usługi (SOA - Service-Oriented Architecture), czyli integracji rozproszonych usług do tworzenia (scalania) złożonych strukturalnie, lecz uproszczonych zewnętrznie z punktu widzenia klienta, elementów konstrukcyjnych architektur rozproszonych.

W skrócie, specyfikacja SCA polega na scalaniu usług rozproszonych w postaci SCAlenia SCA, który jest elementem składowym architektury. Z małych cegiełek tworzymy dom, który staje się częścią osiedla, a każdy z elementów mógłby być reprezentowany jako SCAlenie SCA. Nie byłoby w tym nic szczególnego dla architektów systemów rozproszonych, którzy zwykli korzystać z dorodziejstw platformy Java EE, gdyby nie fakt, że SCA nie określa docelowej platformy, na której uruchomione są usługi jak i sama aplikacja korzystająca z nich - aplikacja scalająca. W przypadku platformy Java EE możemy skorzystać z różnych technologii integrujących, chociażby JAX-WS, które są implementowane z użyciem języka Java, co w przypadku SCA nie jest wymagane. Istnieje realizacja SCA dla języka Java, ale poza tym i C++, Ruby, Python, JavaScript, Groovy, co pozwala na skorzystanie z doświadczenia programistów nie-Java.

SCA jest specyfikacją, w której głównym bytem jest SCAlenie (lub alternatywnie kompozyt, co wydaje się, że gości dosyć często w polskich publikacjach informatycznych, jednakże w przeciwieństwie do SCAlenia nie podkreśla związku modułu ze specyfikacją SCA). Za definicja słowa kompozyt w Słowniku Języka Polskiego PWN:

Materiał złożony z co najmniej dwóch składników, mający właściwości lepsze od nich lub nowe

I tak dokładnie jest w przypadku SCA. Bazując na dostarczaniu funkcjonalności opartej o wykorzystanie zewnętrznych usług (w końcu mamy do czynienia z architekturą zorientowaną na usługi - SOA, więc czego innego moglibyśmy oczekiwać?!) dostarczamy kolejną usługę, tym razem bardziej złożoną i specjalizowaną w postaci SCAlenia. A czym może być usługa? Usługą możemy nazywać publiczny interfejs naszej klasy Java (co implikuje jego wykorzystanie w tradycyjny sposób - z punktu widzenia programisty Java), interfejs biznesowy komponentu EJB, komponent JCA, zasoby zarządzane JMS oraz usługa sieciowa (ang. Web Service). Podkreślić należy, że SCA zakłada znaczne wykorzystanie rozproszonych usług.

Do uruchomienia aplikacji SCA konieczne jest środowisko uruchomieniowe, które udostępniałoby świat zewnętrzny w postaci usług. Jednym z takich środowisk jest Apache Tuscany.

Celem artykułu jest przedstawienie sposóbu tworzenia SCAlenia Dictionary, którego implementacja będzie w języku Java z wykorzystaniem projektów otwartych Apache Tuscany oraz Apache Maven (M2).

Gotowy projekt do uruchomienia (cała struktura projektu wraz z niezbędnymi elementami składowymi opisanymi poniżej) można pobrać jako sca-dictionary.zip. Wystarczy jedynie wykonać polecenie mvn clean test, co spowoduje uruchomienie testu ze SCAleniem.

Spis treści

Utworzenie projektu scalenia - Dictionary

Rozpoczynamy od stworzenia projektu przy pomocy M2 w wybranym przez siebie katalogu, np. C:\.

$ mvn archetype:create -DgroupId=pl.jaceklaskowski.sca.dictionary -DartifactId=sca-dictionary -Dversion=1.0
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ----------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO]    task-segment: [archetype:create] (aggregator-style)
[INFO] ----------------------------------------------------------------------------
...
[INFO] Archetype created in dir: c:\sca-dictionary
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1 second
[INFO] Finished at: Sun May 20 01:28:12 CEST 2007
[INFO] Final Memory: 5M/254M
[INFO] ------------------------------------------------------------------------

Przechodzimy do katalogu sca-dictionary.

$ cd sca-dictionary/

Kolejne polecenia wydawane będą właśnie z tego katalogu, który reprezentuje nasz projekt scalenia SCA.

Zmiany w projekcie najlepiej wykonywać jest za pomocą zintegrowanego środowiska programistycznego (ang. IDE - integrated development environment). Skorzystamy z Eclipse IDE 3.3m7.

Wykonujemy polecenie mvn eclipse:eclipse, aby utworzyć pliku projektu do importu projektu do Eclipse.

$ mvn eclipse:eclipse
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'eclipse'.
[INFO] ----------------------------------------------------------------------------
[INFO] Building sca-dictionary
[INFO]    task-segment: [eclipse:eclipse]
[INFO] ----------------------------------------------------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1 second
[INFO] Finished at: Sun May 20 01:29:15 CEST 2007
[INFO] Final Memory: 3M/254M
[INFO] ------------------------------------------------------------------------

Komunikat BUILD SUCCESSFUL wskazuje na poprawnie wykonane polecenie. Uruchamiamy Eclipse IDE i importujemy projekt (File > Import > Existing Projects into Workspace).

UWAGA: Może się zdarzyć, że po imporcie pojawi się komunikat ostrzegawczy - Unbound classpath variable: M2_REPO/junit/junit/3.8.1/junit-3.8.1.jar in project sca-dictionary - co oznacza brak definicji zmiennej M2_REPO w wybranej przestrzeni roboczej. Mimo, że Eclipse zgłasza to jako komunikat ostrzegawczy, po dodaniu kolejnych zalezności w projekcie nie będzie możliwe tworzenie klas korzystających z nich z poziomu Eclipse. Naprawiamy błąd poprzez Window > Preferences... > Java > Build Path > Classpath Variables > New.... Dodajemy M2_REPO ze wskazaniem na lokalne repozytorium M2, potwierdzamy ponowne przebudowanie projektu i voila - projekt jest czysty, bez jakichkolwiek komunikatów ostrzegawczych, gotowy do dalszego rozwoju.

Modyfikujemy pom.xml o definicje repozytoriów oraz zależności związane z bibliotekami Apache Tuscany i konfigurację wtyczki compiler, ponieważ projekt wymaga Java SE 5.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>pl.jaceklaskowski.sca.dictionary</groupId>
  <artifactId>sca-dictionary</artifactId>
  <packaging>jar</packaging>
  <version>1.0</version>
  <name>SCAlenie Dictionary</name>
  <url>http://www.JacekLaskowski.pl</url>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>

Niepoprawny język.

Musisz wybrać język w następujący sposób: <source lang="html4strict">...</source>

Języki obsługiwane w podświetlaniu składni:

abap, actionscript, actionscript3, ada, apache, applescript, apt_sources, asm, asp, autoit, avisynth, bash, basic4gl, bf, bibtex, blitzbasic, bnf, boo, c, c_mac, caddcl, cadlisp, cfdg, cfm, cil, cmake, cobol, cpp, cpp-qt, csharp, css, d, dcs, delphi, diff, div, dos, dot, eiffel, email, erlang, fo, fortran, freebasic, genero, gettext, glsl, gml, gnuplot, groovy, haskell, hq9plus, html4strict, idl, ini, inno, intercal, io, java, java5, javascript, kixtart, klonec, klonecpp, latex, lisp, locobasic, lolcode, lotusformulas, lotusscript, lscript, lsl2, lua, m68k, make, matlab, mirc, modula3, mpasm, mxml, mysql, nsis, oberon2, objc, ocaml, ocaml-brief, oobas, oracle11, oracle8, pascal, per, perl, php, php-brief, pic16, pixelbender, plsql, povray, powershell, progress, prolog, properties, providex, python, qbasic, rails, rebol, reg, robots, ruby, sas, scala, scheme, scilab, sdlbasic, smalltalk, smarty, sql, tcl, teraterm, text, thinbasic, tsql, typoscript, vb, vbnet, verilog, vhdl, vim, visualfoxpro, visualprolog, whitespace, whois, winbatch, xml, xorg_conf, xpp, z80

          <target>1.5</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <repositories>
    <repository>
      <id>apache.incubator</id>
      <url>http://people.apache.org/repo/m2-incubating-repository</url>
    </repository>
  </repositories>
  <dependencies>
    <dependency>
      <groupId>org.apache.tuscany.sca</groupId>
      <artifactId>tuscany-host-embedded</artifactId>
      <version>1.0-incubating-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>org.apache.tuscany.sca</groupId>
      <artifactId>tuscany-implementation-java-runtime</artifactId>
      <version>1.0-incubating-SNAPSHOT</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Po zmianie zależności ponownie generujemy pliki projektu dla wybranego środowiska programistycznego IDE.

$ mvn eclipse:eclipse
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'eclipse'.
[INFO] ----------------------------------------------------------------------------
[INFO] Building SCAlenie Dictionary
[INFO]    task-segment: [eclipse:eclipse]
[INFO] ----------------------------------------------------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Sun May 20 01:35:43 CEST 2007
[INFO] Final Memory: 6M/254M
[INFO] ------------------------------------------------------------------------

Z BUILD SUCCESSFUL możemy być pewni, że wszystko jest poprawnie zestawione i możemy przejść do kolejnego kroku.

Opcjonalnie możemy jeszcze usunąć klasy pl.jaceklaskowski.sca.dictionary.App oraz pl.jaceklaskowski.sca.dictionary.AppTest, które są całkowicie niepotrzebne w projekcie, a zostały automatycznie utworzone podczas zestawiania projektu korzystając z M2.

Utworzenie interfejsu biznesowego SCAlenia - DictionaryService

Kolejny krok to utworzenie interfejsu biznesowego SCAlenia - DictionaryService.

package pl.jaceklaskowski.sca.dictionary;

public interface DictionaryService {
    String translate(String word, String fromLanguage, String toLanguage);
}

Interfejs składa się z pojedyńczej metody, która przyjmuje słowo do przetłumaczenia z języka wskazanego przez zmienną fromLanguage do toLanguage. W interfejsie nie występują konstrukcje, które związywałyby go z jakąkolwiek technologią, w tym i SCA.

Plik interfejsu - DictionaryService.java - umieszczamy w katalogu src/main/java/pl/jaceklaskowski/sca/dictionary.

Utworzenie implementacji SCAlenia - DictionaryServiceImpl

Kolejnym krokiem jest utworzenie implementacji SCAlenia - DictionaryServiceImpl.

package pl.jaceklaskowski.sca.dictionary;

import org.osoa.sca.annotations.Reference;

public class DictionaryServiceImpl implements DictionaryService {

    private PolishEnglishDictionaryService polishEnglishDictionaryService;

    private EnglishPolishDictionaryService englishPolishDictionaryService;

    @Reference
    public void setEnglishPolishDictionaryService(EnglishPolishDictionaryService englishPolishDictionaryService) {
        this.englishPolishDictionaryService = englishPolishDictionaryService;
    }

    @Reference
    public void setPolishEnglishDictionaryService(PolishEnglishDictionaryService polishEnglishDictionaryService) {
        this.polishEnglishDictionaryService = polishEnglishDictionaryService;
    }

    public String translate(String word, String fromLanguage, String toLanguage) {
        if (fromLanguage.equals("pl") && toLanguage.equals("en")) {
            return polishEnglishDictionaryService.translate(word);
        } else if (fromLanguage.equals("en") && toLanguage.equals("pl")) {
            return englishPolishDictionaryService.translate(word);
        }
        // no translation; pass it along
        return word;
    }
}

W zasadzie możemy powiedzieć, że w klasie nie ma nic, co byłoby specjalnie egzotycznego w stosunku do typowego programowania w Javie. Oczywiście pojawiło się użycie adnotacji @Reference, która należy do pakietu spoza standardowych pakietów Java SE, ale mimo to, klasa nadal nie jest związana z jakąkolwiek technologią, w tym i SCA.

Adnotacja @Reference służy do oznaczenia pola, metody modyfikującej (ang. setter) lub parametru wejściowego konstruktora, gdzie nastąpi przekazanie (wstrzelenie) referencji do usługi (możliwość udekorowania parametru wejściowego konstruktora jest novum w porównaniu z mechanizmem wstrzeliwania zależności w Java EE, w którym taka możliwość nie istnieje).

Implementacja opiera się o wykorzystanie zewnętrznych usług - PolishEnglishDictionaryService oraz EnglishPolishDictionaryService. Ich konfiguracja i złączenie nastąpi w kolejnym kroku za pomocą pliku definiującego scalenie - Dictionary.composite. Środowisko uruchomieniowe (w naszym przypadku Apache Tuscany) podczas uruchomienia scalenia przekaże referencje do usług zewnętrznych. Warty uwagi jest fakt - cecha SCA, że mimo wykorzystania typowych konstrukcji programistycznych w Javie do utworzenia scalenia, położenie pozostałych usług składowych nie musi być w ramach tej samej wirtualnej maszyny Java (JVM), czy nawet maszyny, na której będzie uruchomione scalenie. Pamiętajmy o rozproszeniu usług mimo, że samo tworzenie SCAlenia może prowadzić do złudnego przekonania, że wszystko wykonuje się w ramach pojedyńczej wirtualnej maszyny.

Plik klasy - DictionaryServiceImpl.java - umieszczamy w katalogu src/main/java/pl/jaceklaskowski/sca/dictionary.

Utworzenie usług pomocniczych - PolishEnglishDictionaryService oraz EnglishPolishDictionaryService

Interfejsy i ich realizacje usług pomocniczych PolishEnglishDictionaryService oraz EnglishPolishDictionaryService nie należą do najbardziej interesujących. Wynika to z faktu uproszczenia prezentacji sposobu tworzenia SCAlenia. Nie ma w nich nic co związywałoby je z SCA lub dowolną inną technologią. Nie ma w nich nawet adnotacji SCA. Są one jedynie przykładową realizacją usług składowych, z których skorzysta SCAlenie. Mimo, że usługi zewnętrzne w obecnej postaci nie są zbyt interesujące (są typowymi elementami projektów Java) to należy pamiętać, że same mogłyby być innymi scaleniami bądź interfejsami systemów zewnętrznych, do których dostęp odbywałby się za pomocą technologii należących do rodziny Usług Sieciowych (ang. Web Services), np. SOAP czy WSDL, czy komponentami EJB lub jeszcze innymi zdalnymi usługami w dowolnej technologii, z której możnaby skorzystać z poziomu języka Java (jest to w końcu język, w którym konstruujemy SCAlenie).

Rozpoczniemy od stworzenia interfejsu EnglishPolishDictionaryService.

package pl.jaceklaskowski.sca.dictionary;

public interface EnglishPolishDictionaryService {
    String translate(String word);
}

oraz jego implementacji EnglishPolishDictionaryServiceImpl.

package pl.jaceklaskowski.sca.dictionary;

public class EnglishPolishDictionaryServiceImpl implements EnglishPolishDictionaryService {

    public String translate(String word) {
        return "pl_" + word;
    }

}

Następnie tworzymy kolejny interfejs PolishEnglishDictionaryService.

package pl.jaceklaskowski.sca.dictionary;

public interface PolishEnglishDictionaryService {
    String translate(String word);
}

oraz jego realizacji PolishEnglishDictionaryServiceImpl.

package pl.jaceklaskowski.sca.dictionary;

public class PolishEnglishDictionaryServiceImpl implements PolishEnglishDictionaryService {

    public String translate(String word) {
        return "en_" + word;
    }

}

Interfejsy i klasy realizujące umieszczamy w katalogu src/main/java/pl/jaceklaskowski/sca/dictionary.

Plik konfiguracyjny SCAlenia - Dictionary.composite

Plik konfiguracyjny - Dictionary.composite - jest miejscem, w którym następuje zadeklarowanie klas składowych SCAlenia oraz zależności zewnętrznych, które będą materializowane i przekazane do SCAlenia w miejscach udekorowanych przez adnotację @Resource. W zasadzie struktura i znacznie poszczególnych elementów pliku jest na tyle czytelna, że nie wymaga obszernego wprowadzenia.

<?xml version="1.0" encoding="UTF-8"?>

<composite xmlns="http://www.osoa.org/xmlns/sca/1.0" name="Dictionary">

  <component name="DictionaryServiceComponent">
    <implementation.java class="pl.jaceklaskowski.sca.dictionary.DictionaryServiceImpl" />
    <reference name="polishEnglishDictionaryService" target="PolishEnglishDictionaryComponent" />
    <reference name="englishPolishDictionaryService" target="EnglishPolishDictionaryServiceComponent" />
  </component>

  <component name="PolishEnglishDictionaryComponent">
    <implementation.java class="pl.jaceklaskowski.sca.dictionary.EnglishPolishDictionaryServiceImpl" />
  </component>

  <component name="EnglishPolishDictionaryServiceComponent">
    <implementation.java class="pl.jaceklaskowski.sca.dictionary.PolishEnglishDictionaryServiceImpl" />
  </component>

</composite>

Elementem głównym pliku jest composite z atrybutem name określającym nazwę SCAlenia. W ramach composite znajdziemy deklaracje dostępnych usług (komponentów) składowych - element component. Naszym językiem implementacji SCAlenie i jego usług składowych jest język Java i stąd użycie elementu implementation.java. Wyróżniamy następujące elementy określające różne typy implementacji usług:

  • implementation.java - usługa realizowana w Javie
  • implementation.bpel - usługa realizowana korzystając z BPEL
  • implementation.composite - usługa będąca SCAleniem
  • implementation.spring - usługa konstruowana przy pomocy Spring Framework
  • implementation.ejb - usługa oparta o EJB

Każdy z typów posiada atrybuty, które precyzują usługę. W przypadku usługi realizowanej w Javie będzie to atrybut class, a w BPEL process, itp.

Plik konfiguracyjny SCAlenia - Dictionary.composite - umieszczamy w katalogu src/main/resources.

Klasa testująca SCAlenie - DictionaryTestCase

Za pomocą DictionaryTestCase zweryfikujemy poprawne utworzenie SCAlenia Dictionary uruchamiając je w Apache Tuscany.

package pl.jaceklaskowski.sca.dictionary;

import junit.framework.TestCase;

import org.apache.tuscany.sca.host.embedded.SCADomain;

public class DictionaryTestCase extends TestCase {
    private DictionaryService dictionaryService;
    private SCADomain scaDomain;

    protected void setUp() throws Exception {
        scaDomain = SCADomain.newInstance("Dictionary.composite");
        dictionaryService = scaDomain.getService(DictionaryService.class, "DictionaryServiceComponent");
    }

    public void testDictionary() throws Exception {
        assertEquals(dictionaryService.translate("service", "pl", "en"), "pl_service");
        assertEquals(dictionaryService.translate("architektura", "en", "pl"), "en_architektura");
    }

    protected void tearDown() throws Exception {
        scaDomain.close();
    }
}

Jest to klasa testująca korzystająca z JUnit 4.2, który jest jedną z zależności testowych w projekcie (patrz pom.xml). Uruchomienie SCAlenia to utworzenie egzemplarza domeny SCA zarządzanej przez Apache Tuscany za pomocą statycznej metody SCADomain.newInstance, której parametrem jest nazwa pliku konfiguracyjnego SCAlenia. Wywołanie metody SCADomain.getService to utworzenie egzemplarza SCAlenia. Przypomnijmy, że w pliku konfiguracyjnym deklarowaliśmy usługi składowe SCAlenia, które mogłybyć usługami zdalnymi.

Plik klasy testowej - DictionaryTestCase.java - umieszczamy w katalogu src/test/java/pl/jaceklaskowski/sca/dictionary.

Wykonanie testu pozostawiamy na kolejny krok.

Uruchomienie SCAlenia - wykonanie klasy testowej

Projekt zarządzany jest przez M2, więc wszystkie polecenia wydawane w projekcie są wykonywane z jego pomocą. Uruchomienie SCAlenia to de facto uruchomienie testu jednostkowego poleceniem mvn clean test.

$ mvn clean test
[INFO] Scanning for projects...
[INFO] ----------------------------------------------------------------------------
[INFO] Building SCAlenie Dictionary
[INFO]    task-segment: [clean, test]
[INFO] ----------------------------------------------------------------------------
...
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running pl.jaceklaskowski.sca.dictionary.DictionaryTestCase
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.719 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6 seconds
[INFO] Finished at: Sun May 20 01:25:58 CEST 2007
[INFO] Final Memory: 7M/254M
[INFO] ------------------------------------------------------------------------

Ponownie BUILD SUCCESSFUL jest oznaką poprawnego wykonania polecenia, jak i testu, a tym samym i SCAlenia.

Zbudowanie wersji dystrybucyjnej SCAlenia to wykonanie polecenia mvn clean package, które utworzy plik - sca-dictionary-1.0.jar w katalogu target.

$ mvn clean package
[INFO] Scanning for projects...
[INFO] ----------------------------------------------------------------------------
[INFO] Building SCAlenie Dictionary
[INFO]    task-segment: [clean, package]
[INFO] ----------------------------------------------------------------------------
...
[INFO] Building jar: c:\sca-dictionary\target\sca-dictionary-1.0.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5 seconds
[INFO] Finished at: Sun May 20 01:49:55 CEST 2007
[INFO] Final Memory: 7M/254M
[INFO] ------------------------------------------------------------------------

I to na tyle! Była to znakomita rozgrzewka przed tworzeniem bardziej skomplikowanych SCAleń z użyciem EJB, BPEL oraz Spring Framework. Miłego SCAlania!

Osobiste