Dağıtık uygulama geliştirmek, geliştirilen uygulamayı çalışır halde tutmak, izlemek ve hata ayıklamak zordur. Java EE 6 ve 7 platformu bu zorluğun üstesinden gelmek için çeşitli çözümler sunmaktadır. Java EE 5 ile birlikte programlama modelinde önemli bazı değişiklikler gerçekleşmiştir. Programlama modelinin ve mimarinin temel özellikleri aşağıdaki gibi sıralanabilir:
- Bileşen Tabanlı Programlama
- Sıradan Java Sınıflarının Kullanımı
- Betimsel Programlama
Sınıflar tek bir sorumluluğa sahip ya da tek bir konuda uzman olacak şekilde tasarlanır. Böylelikle sınıflar sadece uzmanlık alanlarında diğer sınıflara hizmet verirler. Bazen bir sınıf uzmanlığı dışında bir iş yapması gerektiğinde, konunun uzmanı sınıf hangisi ise ondan hizmet alır ve isteği bu sınıfa ihale eder. Bu şekilde sınıflar arasında hizmet alma-verme ilişkisi doğar. Bu ilişkinin gevşek bağlı olacak şekilde düzenlenmesi, yazılımdaki değişimin yönetilebilmesi için çok önemlidir. Bunun için hizmet alan ve hizmet veren sınıf arasına bir arayüz yerleştirilir. Bu arayüz bir sözleşme işlevi görür. Sınıflar biri birlerinden bu sözleşmeye uygun olarak hizmet alır ve hizmet verirler. Java programlama dilinde nesneler bu şekilde biri birlerinden hizmet alırlarken, istekler senkron olarak karşılanır. Hizmet alan sınıf cevap gelene kadar bekler. Ancak çok-lifli (=multi-threaded) programlama ile asenkron bir yapı kurulabilir. Hizmet alan ve hizmet veren sınıf arasında asenkron bir yapı kurmanın bir başka yöntemi araya bir mesajlaşma sistemi yerleştirmektir. Hizmet alan nesne isteğini bir mesaj olarak mesajlaşma sistemine iletir ve yoluna devam eder. Hizmet veren nesne mesajı alır ve işler.
Message-Driven Bean (MDB) kullanmak için JMS (Java Messaging System) sunucusuna ihtiyaç duyulur. İstemci isteğini bir mesaj olarak kuyruğa yerleştirir ve yoluna devam eder. Hizmet veren sınıf, MDB, vakit buldukça kendisine gelen mesajları alır ve işler. Eğer işlem mili saniye mertebesinde sürelerde tamamlanabilecek sürelerde ise Session Bean, saniye, dakika mertebesinde ya da daha da uzun soluklu ise MDB olarak gerçeklenir. Genellikle uygulama sunucuları üzerinde birer JMS sistemi bulunur. İstenirse JMS sunucusu ayrı olarak da kurulabilir. JMS üzerinden hizmet alan ve veren bileşenler böylelikle fiziksel olarak farklı makinalarda olabilir. Üstelik hizmet veren bileşen, Java EE'de bu MDB olacaktır, henüz ayakta olmasa bile sorun çıkmaz. Bir süre sonra MDB çalışmaya başlar ve kuyrukta biriken mesajları alarak gereğini yerine getirmeye ve iş mantığını çalıştırmaya başlar.
JMS üzerinden hem tasarımsal hem de fiziksel olarak gevşek bağlı yapılar oluşturulabilir. JMS tabanlı çözümün en önemli yitimi, asenkron olarak iletilen isteğin sonucu için hazırda bir çözümün olmayışıdır. İşlemin sonucunu veritabanı üzerinden ya da başka bir mesaj kuyruğu üzerinden alınabileceği çözümler kurulabilse de bu mimariyi biraz karmaşık hale getirecektir.
Java EE 6 ile birlikte yeni bir bileşen modelimiz daha var: CDI (Contexts and Dependency Injection) Bean. CDI en temelde hizmet alan ve veren bileşenler arasındaki bağımlılıkları yöneten bir çatı sunuyor. Tıpkı Spring, Guice, JBoss Seam gibi. Bunun dışında ilgiye dayalı programlama, olay temelli programlama ve dekoratör tasarım kalıbı gibi çözümlere hızlı bir şekilde ulaşmamızı sağlar. CDI'nın şartnamesine bu bağlantıdan erişebilirsiniz. CDI Java EE'deki diğer tüm API'lerle ve onların bileşenleriyle tümleşik çalışacak şekilde tasarlanmıştır.
Bu yazıda CDI çatısının olay temelli programlama çözümünü inceleyeceğiz. İncelememizi Wildfly üzerinde çalışan bir Java EE 7 uygulaması üzerinde gerçekleştireceğiz. Öncelikle bir olay sınıfı tasarlamamız gerekiyor. Olay sınıfının hiç bir özelliği bulunmayan, yalın bir Java sınıfı olması istenir. Sadece özniteliklerden oluşan ve durumu değiştirilemez bir sınıf olması tercih edilir. Bu anlamda aykırı durumlar için tasarladığımız Exception sınıfları ile benzerlik gösterir. Exception sınıflarının da sadece hatayı açıklayan öznitelikler içermesini isteriz. Exception sınıfının kesinlikle hatayı çözmeye çalışan metodlar içermemelidir. Örnek uygulamamızda 1000 liranın üzerindeki para akışlarının bir web sayfasından izlenmesi sağlanıyor. Para transferi iş mantığı katmanında bir CDI bileşeni tarafından gerçekleniyor. Önce olay sınıfına bir bakalım:
package com.example.event; import com.example.domain.Account; public class OverdraftEvent { private Account account; private double amount; public OverdraftEvent(Account account, double amount) { this.account = account; this.amount = amount; } public Account getAccount() { return account; } public double getAmount() { return amount; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((account == null) ? 0 : account.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; OverdraftEvent other = (OverdraftEvent) obj; if (account == null) { if (other.account != null) return false; } else if (!account.equals(other.account)) return false; return true; } @Override public String toString() { return "OverdraftEvent [account=" + account + ", amount=" + amount + "]"; } }
AccountService.java:
package com.example.service; public interface AccountService { boolean transferMoney(String from, String to, double amount); }
package com.example.service.impl;
import javax.enterprise.event.Event; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import com.example.dao.AccountDao; import com.example.domain.Account; import com.example.domain.InsufficientBalance; import com.example.event.OverdraftEvent; import com.example.service.AccountService; @Named @Singleton public class SimpleAccountService implements AccountService { @Inject private Event<OverdraftEvent> event; @Inject private AccountDao accountDao; @Override public boolean transferMoney(String from, String to, double amount) { Account fromAccount = accountDao.findAccountByIban(from); if (fromAccount == null) return false; Account toAccount = accountDao.findAccountByIban(to); if (toAccount == null) return false; try { fromAccount.withdraw(amount); toAccount.deposit(amount); if (amount > 1_000) { OverdraftEvent oe = new OverdraftEvent(fromAccount, amount); System.err.println("Firing an event: " + oe); event.fire(oe); } } catch (InsufficientBalance e) { return false; } return true; } }
- JMS ile kümeleme yapılabilir. CDI ise sadece aynı Java Sanal Makinasındaki bileşenler için yerel bir çözüm üretir.
- JMS mesajların alındığını garanti eder. Hata oluşursa tekrar deneyebilir.
- JMS sunucuları dağıtık atomik işlemlerde yer alabilir.
package com.example.web.endpoint; import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.Set; import javax.enterprise.event.Observes; import javax.inject.Singleton; import javax.websocket.EncodeException; import javax.websocket.OnClose; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import com.example.event.OverdraftEvent; @ServerEndpoint(value="/monitor",encoders=OverdraftEventEncoder.class) @Singleton public class AccountMonitor { private static Set<Session> peers = Collections .synchronizedSet(new HashSet<Session>()); public AccountMonitor() { System.err.println("AccountMonitor is created!"); } @OnOpen public void onOpen(Session peer) { System.err.println("Session is created: " + peer); peers.add(peer); } @OnClose public void onClose(Session peer) { System.err.println("Session is closed: " + peer); peers.remove(peer); } public void sendEvent(@Observes OverdraftEvent event) { System.err.println("Event has been received!"); System.err.println(event); for (Session peer : peers) { try { peer.getBasicRemote().sendObject(event); } catch (IOException | EncodeException e) { e.printStackTrace(); } } } }
Tüm uygulamanın kaynak koduna bu bağlantıdan ulaşabilirsiniz. CDI ve Java EE 7 ile ilgili daha detaylı bilgi edinmek için "Developing Enterprise Applications on Java EE 7" ve "Contexts and Dependency Injection (CDI)" eğitimlerinin içeriklerini inceleyebilirsiniz.
No comments:
Post a Comment