TomEE, CDI a remote EJB.
09.04.2017

Název dnešního článku vypadá trošku krypticky, ale myslím, že ti, které by mohl zaujmout, jistě tuší o čem si budeme povídat. Vracíme se k programovacímu jazyku Java, konkrétně k tématům z Enterprise Edition (EE) Javy. Předpokládám, že čtenář je obeznámen s tvorbou webových aplikací v Javě EE.

Hned na začátku bychom si měli popsat, alespoň rámcově, použité technologie. Pokud vyvíjíte aplikaci v Java EE, potřebujete aplikační server, na kterém bude aplikace běžet. Aplikačních serverů existuje více – od open source (a obvykle zdarma dostupných) jako například Glassfish, Wildfly nebo Apache TomEE až po komerční jako Oracle Weblogic nebo IBM WebSphere. Já jsem vybral Apache TomEE především proto, že je kompatibilní s Apache Tomcat, což byl jeden z požadavků. Apache Tomcat je velice populární servlet kontejner a TomEE je v podstatě Tomcat rozšířený o implementaci Javy EE. V aktuální verzi 7.x implementuje Java EE 7 standard, což byl druhý požadavek.

Motivace.

Pokud vytváříme monolitickou (všechny části aplikace jsou součástí distribučního WAR archivu) Java EE web aplikaci s uživatelským rozhraním implementovaným pomocí JSF a k propojení jednotlivých komponent aplikace používáme CDI, vše je snadné. Jestliže potřebujeme například v backing bean volat metody ze servisní třídy, použijeme něco takového:

@Named
@RequestScope
public class MyBean {
    @Inject
    private MyService myService;
    …
}

Metody potom můžeme volat třeba myService.myMethod(); – CDI se již postará o přiřazení správné instance do myService. Situace je znázorněna na Obr. 1.


Obr. 1

Pokud však nastane situace, kdy potřebujeme mít servisní třídy na fyzicky odděleném serveru, jako na Obr. 2, je situace složitější.


Obr. 2

Na serveru Server 2 je umístěna pouze servisní vrstva, která má přístup například k databázi, je bezstavová a vrací pouze data pro další zpracování – nemá žádné uživatelské rozhraní. Naopak na serveru Server 1 (a takových serverů může být více) je nasazena pouze část aplikace odpovědná za uživatelské rozhraní. Tato část aplikace nemá informace o databázi a dalších technologiích v pozadí – zná pouze Server 2 na který se napojuje.

Jak na to?

Je zřejmé, že musíme nějakým způsobem zajistit komunikaci mezi oběma servery. K tomu lze použít například univerzální SOAP nebo REST protokol. Pokud jsou však obě části aplikace implementované v Javě, můžeme s výhodou použít Remote EJB. Technologie Remote EJB (Enterprise Java Bean) umožňuje volání metod objektů umístěných na fyzicky odděleném serveru stejně, jako by byly dostupné lokálně. Vzhledem k tomu, že jak EJB server, tak klient poběží na rozdílných serverech, musíme deklarovat rozhraní (interface) pro servisní třídy a případné datové objekty do zvláštního balíčku (například *.jar souboru). Tento balíček pak musíme použít jak na straně EJB serveru (kde budou rozhraní implementována), tak na straně klienta (kde budou rozhraní volána). Pokud se podíváme na Obr. 2, EJB serverem je myšlen Server 2 a EJB klientem pak Server 1.

Bylo by dobré, pokud by volání služeb ze vzdáleného serveru bylo stejně komfortní, jako použití lokálních služeb. Tedy abychom mohli pro vzdálenou servisní třídu použít jednoduchou CDI @Inject anotaci. Dosáhnout toho můžeme elegantně pomocí CDI produceru.

@Named
@ApplicationScoped
public class RemoteEjbProducer {

    @Produces
    public Service1Ejb getService1Ejb() {
        Service1Ejb service1Ejb = null;
        try {
            service1Ejb = getRemoteEjb("Service1EjbImplRemote");
        } catch (NamingException e) {
            // handle error
        }
        return service1Ejb;
    }

    private  T getRemoteEjb(String ejbName) throws NamingException {
        T result;
        Properties properties = new Properties();
        properties.put("java.naming.factory.initial", "org.apache.openejb.client.RemoteInitialContextFactory");
        properties.put("java.naming.provider.url", "http://localhost:8080/services/ejbs/");
        InitialContext initialContext = new InitialContext(properties);
        result = (T) initialContext.lookup(ejbName);
        return result;
    }
}

Service1EjbImplRemote je název registrované servisní třídy. Samotná třída je pojmenovaná Service1EjbImpl, Remote k názvu přidává samotný server. Třída je anotovaná pomocí @Remote, aby byla volatelná přes Remote EJB rozhraní. Adresa http://localhost:8080/services/ejbs/ je pak adresa serveru s Remote EJB, servisní třídy jsou deployované v kontextu services – aplikace je na server deployovaná jako services.war. Potom už můžeme kdekoliv v CDI beaně použít:

@Named
@RequestScope
public class MyBean {
    @Inject
    private Service1Ejb service1Ejb;
    …
}
s tím, že Service1Ejb interface je definovaný v samostatném balíčku, použitém jak na Remote EJB serveru, tak i klientu.

Poslední věc, kterou je třeba nastavit, je bezpečnostní politika. TomEE totiž z bezpečnostních důvodů standardně blokuje jákékoliv volání Remote EJB. Proto musíme v konfiguračním souboru system.properties nastavit:

tomee.serialization.class.blacklist = -
tomee.serialization.class.whitelist = *
čímž povolíme vzdálené spouštění všech tříd. To je dobré pro vývoj, pro produkční nasazení bude lépe nastavit volby restriktivněji, viz http://tomee.apache.org/ejbd-transport.html.

Závěr.

U aplikačního serveru TomEE je možné použít také jiný přístup, ale ten jsem zatím nezkoušel. Můžete se podívat na http://tomee.apache.org/ejb-refs.html. Výhodou postupu, popsaného v tomto článku je, že jej lze aplikovat i na jiné aplikační servery (po menších úpravách), viz Stack Overflow.