SOA-arkkitehtuurissa palvelut ovat tilattomia ja proseduraalisia, joukko operaatioita ja niihin liittyviä rakenteitä. Lähestymistapa on siten vastakohta Domain driven -tyyppiselle ohjelmoinnille, jossa oliot ovat tilallisia ja huolehtivat itse operaatioista.

2.4.1 Rajapintoihin liittyvät ongelmat

Sisäisten rakenteiden näkyminen rajapinnassa

Palvelurajapinnat ovat julkisia ja niihin liittyy useampia käyttöliittymiä tai integraatioita. SOA-arkkitehtuurissa täytyy huolehtia siitä, etteivät tietokantaan tehtävät muutokset heijastu rajapintaan ja riko siihen yhteydessä olevia käyttöliittymiä. Jos esimerkiksi tietojen tallennukseen on käytetty JPA (Hibernate) tekniikkaa, on huolehdittava siitä, ettei tietokantatauluja kuvaavia entity-olioita ole käytetty julkaistussa rajapinnassa parametreinä. Jos näin on tehty, rajapinta ei voi olla muuttumaton (tai ainakin erittäin vaikea pitää sellaisena), eikä ratkaisu tuo oikeastaan mitään lisäarvoa siihen että oltaisiin suoraan yhteydessä tietokantaan JDBC:n avulla. Tämä ongelma korostuu SOA-ratkaisussa, jossa lähtökohtaisesti rajapinnat ovat kenen tahansa muun palvelun käytettävissä (ei välttämättä ole edes saman toimittajan tekemiä).

Taaksepäin yhteensopivuus

Jossain palvelun kehityskaaren vaiheessa voi tulla tarvetta muuttaa julkaistua rajapintaa. Esimerkiksi halutaan tehdä uusi toiminto, jossa toimintalogiikkaa tulee muuttaa tai jokin tietokantakenttä poistetaan käytöstä. Tällöin on huolehdittava siitä, että rajapintaan vasten olevat käyttöliittymät ja integraatiot eivät rikkoudu ja että niiden päivittäminen voidaan tehdä hallitusti. J2EE:ssä ei ole mahdollisuutta versioida pakkauksia siten, että niitä voitaisiin ajaa samanaikaisesti muuten kuin pakkausten nimeä muuttamalla.

2.4.2 Peppi-arkkitehtuurissa käytettävä ratkaisu rajapintojen tekemiseen

Sisäisten rakenteiden näkyminen rajapinnassa

Peppi-projektin yhteydessä on tehty Kuali-moduulien selvitystyötä sekä havaittu tiettyjen Kualissa käytettävien ratkaisumallien olevan hyödyllisiä Peppi-arkkitehtuurissa. Seuraavaksi on käyty esimerkinomaisesti läpi Kuali Student -moduulin rajapintojen toteutus sekä arvioitu ratkaisumalleja käytettäväksi Peppi-arkkitehtuurissa.

Kuali Student -projektin rajapintojen toteutus

Rajapinta

LuService-rajapinta sijaitsee ks-lum-api/src/main/java kansiossa org.kuali.student.lum.lu.service pakkauksessa. Rajapinta huolehtii learning unit -olioihin, esimerkiksi opintojakso, liittyvistä toiminnoista.  Rajapinta sisältää esimerkiksi seuraavan opintojakson perustiedot palauttavan metodin.

/**
     * Retrieves core information about a CLU
     * @param cluId identifier of the CLU
     * @return information about a CLU
     * @throws DoesNotExistException cluId not found
     * @throws InvalidParameterException invalid cluId
     * @throws MissingParameterException missing cluId
     * @throws OperationFailedException unable to complete request
     */
    public CluInfo getClu(@WebParam(name="cluId")String cluId) throws DoesNotExistException, InvalidParameterException,
MissingParameterException, OperationFailedException;

Metodi palauttaa DTO-olion(org.kuali.student.lum.lu.dto.CluInfo), joka sijaitsee ks-lum-api/src/main/java kansiossa org.kuali.student.lum.lu.dto pakkauksessa.

Toteutus

Clu-luokka (canonical learning unit, joka kuvaa esimerkiksi opintojaksoa) on JPA-tekniikan avulla tehty rakenneluokka, jonka avulla tiedot voidaan tallentaa tietokantaan. Se sijaitsee ks-lum-impl/src/main/java kansiossa org.kuali.student.lum.lu.entity pakkauksessa.

LuServiceImpl toteuttaa LuService-rajapinnan ja sijaitsee org.kuali.student.lum.lu.service.impl pakkauksessa. LuServiceImpl luokka sisältää myös JAX-WS annotaatiot, jotta samaa luokkaa käytetään webservice-endpoint luokkana.

@WebService(endpointInterface = "org.kuali.student.lum.lu.service.LuService", serviceName = "LuService",
portName = "LuService", targetNamespace = "http://student.kuali.org/wsdl/lu")
@Transactional(noRollbackFor={DoesNotExistException.class},rollbackFor={Throwable.class})
public class LuServiceImpl implements LuService {

Rajapinnassa käytettävät rakenteet on eristetty DTO-olion avulla persistoitavasta Clu-oliosta siten että käytetään LuServiceAssembler luokkaa muuttamaan moduulin sisäinen Clu-olio rajapinnassa käytettäväksi CluInfo-olioksi.

getClu metodin toteutus:

	@Override
	public CluInfo getClu(String cluId) throws DoesNotExistException,
			InvalidParameterException, MissingParameterException,
			OperationFailedException {

		checkForMissingParameter(cluId, "cluId");

		Clu clu = luDao.fetch(Clu.class, cluId);
		return LuServiceAssembler.toCluInfo(clu);
	}

Assembler muuttaa olion toiseksi toCluInfo- metodin avulla

public static CluInfo toCluInfo(Clu entity) {
		if (entity == null) {
			return null;
		}
		CluInfo dto = new CluInfo();

		// copy all simple fields - exclude complex data types
		BeanUtils.copyProperties(entity, dto, new String[] {
				"officialIdentifier", "alternateIdentifiers", "descr",
				"participatingOrgs", "primaryInstructor", "instructors",
				"stdDuration", "luCodes", "credit", "offeredAtpTypes", "fee",
				"accounting", "intensity",
				"campusLocationList", "accreditationList",
				"adminOrgs", "attributes", "metaInfo", "versionInfo" });
		dto.setOfficialIdentifier(toCluIdentifierInfo(entity
				.getOfficialIdentifier()));
		dto.setAlternateIdentifiers(toCluIdentifierInfos(entity
				.getAlternateIdentifiers()));
		dto.setDescr(toRichTextInfo(entity.getDescr()));

		// accreditingOrg Deprecated in v 1.0-rc2 Replaced by Primary and
		// Alternate admin orgs
		dto.setAccreditations(toAccreditationInfos(entity.getAccreditations()));

		dto.setAdminOrgs(toCluAdminOrgInfos(entity
				.getAdminOrgs()));

		dto.setPrimaryInstructor(toCluInstructorInfo(entity
				.getPrimaryInstructor()));
		dto.setInstructors(toCluInstructorInfos(entity.getInstructors()));
		dto.setStdDuration(toTimeAmountInfo(entity.getStdDuration()));
		dto.setLuCodes(toLuCodeInfos(entity.getLuCodes()));

		if (entity.getOfferedAtpTypes() != null) {
			List<String> offeredAtpTypes = new ArrayList<String>(entity
					.getOfferedAtpTypes().size());
			for (CluAtpTypeKey key : entity.getOfferedAtpTypes()) {
				offeredAtpTypes.add(key.getAtpTypeKey());
			}
			dto.setOfferedAtpTypes(offeredAtpTypes);
		}

		dto.setFeeInfo(toCluFeeInfo(entity.getFee()));
		dto.setAccountingInfo(toCluAccountingInfo(entity.getAccounting()));

		dto.setAttributes(toAttributeMap(entity.getAttributes()));
		dto.setMetaInfo(toMetaInfo(entity.getMeta(), entity.getVersionNumber()));

		dto.setType(entity.getLuType().getId());

		if (entity.getCampusLocations() != null) {
			List<String> campusLocations = new ArrayList<String>(entity
					.getCampusLocations().size());
			for (CluCampusLocation cluCamp : entity.getCampusLocations()) {
				campusLocations.add(cluCamp.getCampusLocation());
			}
			dto.setCampusLocations(campusLocations);
		}

		dto.setIntensity(toAmountInfo(entity.getIntensity()));

		dto.setVersionInfo(toVersionInfo(entity.getVersion()));

		return dto;

	}

Kuten tästä metodista voi nähdä entity-olio ja rajapinnassa julkaistu Dta-olio sisältävät samat attribuutit ja operaatio tuntuu tarpeettoman monimutkaiselta. Tämä on kuitenkin keino SOA-arkkitehtuurissa huolehtia siitä, että julkaistu rajapinta ei muuttuisi. Toteutus käyttää Apachen BeanUtils-kirjastoa helpottamaan tietojen kopiointia dto-olioon.

Yhteenveto Kuali Student -projetissa käytettävälle ratkaisulle

Hyötyjä DTO-suunnittelumallissa ovat:

  • Julkaistu rajapinta saadaan eristettyä moduulin sisäisitä rakenteista ja palvelun kehittämiselle jää pelivaraa
  • Voidaan optimoida julkaistua rajapintaa, kaikki tallennettuja tietoja ei välttämättä tarvitse siirtää
  • Voidaan tehdä toinen "näkymä", toinen julkinen rajapinta, joka käyttää eri dto-olioita

Heikkouksia:

  • Lisää palvelun monimutkaisuutta ja tietoja täytyy muuttaa useampaan paikkaan.

Peppi-arkkitehtuurissa käytettävä ratkaisu

Ratkaisumallina noudatetaan Kuali Student-projektissa esitettyä tapaa, rajapinnat eristetään moduulin sisäisistä rakenteista DTO-suunnittelumallin avulla.

Rajapinnat tehdään omiin pakkauksiin moduulikohtaisesti.

Dto-olioiden rakentamisessa hyödynnetään BeanUtils-kirjastoa kuten Kuali student -projektissa. Toinen tapa rakentaa Dto-oliot on käyttää Builder-mallia. Tässä mallissa Dto-luokka sisältää staattisen Builder-luokan joka palauttaa Dto-olion.

public class CourseunitDto implements Serializable {

private String name; //setters and getters

private CourseunitDto() {}

public static class Builder {

   private CourseunitDto courseunitDto;

  Builder() {
     this.courseunitDto = new CourseunitDto();
  }
  public Builder name(String name) {

   if (name == null) {
      throw new IllegalArgumentException("Course should have a name");
   }

   this.courseunitDto.setName(name);
   return this;
 }

 public CourseunitDto build() {
   return this.courseunitDto; }
}
}

Tällöin dto-olio voidaan rakentaa seuraavasti.

new CourseunitDto.Builder.name("test").build();

Taaksepäin yhteensopivuus

Käytetään OSGI-teknologiaa tehdään palvelut OSGI-bundleina, joita ajetaan Servicemix4-alustassa. OSGI mahdollistaa jar-pakkauksen(OSGI-bundle) eri versioiden ajamisen rinnakkain.

  • No labels
You must log in to comment.