Monday, February 10, 2014

Spring çatısında Olaya Dayalı Programlama

Tasarım yaparken, değişiklikleri daha iyi yönetebilmek amacıyla, sınıflar arasında gevşek bağlı bir yapı oluşturulmaya çalışılır. Bu amaçla öncelikle arayüz (=interface) tanımlanır. Bu arayüz bir tür sözleşme işlevi görür. Hizmet alan ve veren sınıflar biri birlerini doğrudan görmezler. Hizmet sağlayan sınıf bu arayüze ya da sözleşmeye  uygun olarak hizmet verir, istemci sınıf ise yine bu sözleşmeye uygun olarak hizmet alır. Böylelikle, ileride servis sağlayıcı sınıf değiştiğinde ya da başka bir sınıftan hizmet alındığında istemci sınıfta bir değişiklik yapmak gerekmez. Dört kafadarın (=GoF, Gang of Four) yayınladığı tasarım kalıpları incelendiğinde, bu şablon hemen hemen tüm kalıplarda karşımıza çıkar. Gevşek bağlı yapı oluşturmak için düşünülen mimari çözümlerden biri de Olaya Dayalı Programlamadır (Event Driven Programming).  Burada öncelikle olay tanımlanır. Sınıflardan biri belirli bir koşul gerçekleştiğinde olay nesnesini yayınlar. Bu olayın gerçekleşmesini gözlemlemek isteyen sınıflar ise olay için abone olurlar ve olay gerçekleştiğinde kayıtlı tüm nesnelere haber verilir. Bu yapıyı Spring çatısında gerçeklemek oldukça kolaydır. Öncelikle olay sınıfı, LargeAmountTransferEvent, oluşturulur:

package com.example.events;

import org.springframework.context.ApplicationEvent;

public class LargeAmountTransferEvent extends ApplicationEvent {
 private double amount;

 public LargeAmountTransferEvent(double amount, Object source) {
  super(source);
  this.amount = amount;
 }

 public double getAmount() {
  return amount;
 }

}

Gözlemci sınıfı oluşturmak için ilgili sınıfta Spring'in ApplicationListener arayüzünü gerçeklemek yeterli olur:

package com.example.events;

import java.sql.Date;
import java.sql.Timestamp;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class LargeAmountTransferListener implements ApplicationListener<LargeAmountTransferEvent>{

 @Override
 public void onApplicationEvent(LargeAmountTransferEvent event) {
  System.out.println("Large amount transfer has occured: "+event.getAmount()+" at "+new Timestamp(event.getTimestamp()));
 }

}

com.example.service.AccountService.java:
package com.example.service;

import com.example.domain.Account;

public interface AccountService {
 boolean transferMoney(Account from,Account to,double amount);
}

Olayın yayınlanacağı sınıfın ise Spring'in ApplicationEventPublisherAware arayüzünü gerçeklemesi gerekir.
Bu arayüzde setApplicationEventPublisher isimli ek bir metot yer alır. Olay ise Spring IoC kabı tarafından sağlanan ApplicationEventPublisher sınıfından bir nesne aracılığı ile yayınlanır:

com.example.service.AccountServiceImpl.java:
package com.example.service;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

import com.example.domain.Account;
import com.example.domain.DeficitException;
import com.example.events.LargeAmountTransferEvent;

@Service("accountsrv")
public class AccountServiceImpl implements AccountService,
  ApplicationEventPublisherAware {
 private ApplicationEventPublisher publisher;

 public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
  this.publisher = publisher;
 }

 public boolean transferMoney(Account from, Account to, double amount) {
  if (amount >= 100_000_000)
   publisher.publishEvent(new LargeAmountTransferEvent(amount, this));
  boolean withdraw = false;
  boolean deposit = false;
  try {
   from.withdraw(amount);
   withdraw = true;
   to.deposit(amount);
   deposit = true;
  } catch (Exception e) {
   if (withdraw)
    from.deposit(amount);
   if (deposit)
    try {
     to.withdraw(amount);
    } catch (DeficitException e1) {
     return false;
    }
  }
  return true;
 }
}

com.example.domain.Account.java:
package com.example.domain;

public class Account {
 private String accountId;
 private double balance;

 public Account() {
 }

 public Account(String accountId, double balance) {
  this.accountId = accountId;
  this.balance = balance;
 }

 public String getAccountId() {
  return accountId;
 }

 public double getBalance() {
  return balance;
 }

 public void withdraw(double amount) throws DeficitException {
  if (amount <= 0.0)
   throw new IllegalArgumentException(
     "Amount cannot be negative or zero!");
  if (amount > balance)
   throw new DeficitException(
     "Your balance dows not cover your expenses!", amount
       - balance);
  balance -= amount;
 }

 public void deposit(double amount) {
  if (amount <= 0.0)
   throw new IllegalArgumentException(
     "Amount cannot be negative or zero!");
  balance += amount;
 }

 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result
    + ((accountId == null) ? 0 : accountId.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;
  Account other = (Account) obj;
  if (accountId == null) {
   if (other.accountId != null)
    return false;
  } else if (!accountId.equals(other.accountId))
   return false;
  return true;
 }

 @Override
 public String toString() {
  return "Account [accountId=" + accountId + ", balance=" + balance + "]";
 }
}

com.example.domain.DeficitException.java:
package com.example.domain;

public class DeficitException extends Exception {
 private double deficit;

 public DeficitException(String message, double deficit) {
  super(message);
  this.deficit = deficit;
 }

 public double getDeficit() {
  return deficit;
 } 
}

com.example.config.AppConfig.java:
package com.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import com.example.domain.Account;

@Configuration
@ComponentScan({"com.example.service","com.example.events"})
public class AppConfig {
 @Bean
 public Account acc1(){
  return new Account("TR123456789", 100_000_000);
 }
 @Bean
 public Account acc2(){
  return new Account("TR987654321", 250_000_000);
 }
}

com.example.TestAppConfig.java:
package com.example;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.example.config.AppConfig;
import com.example.domain.Account;
import com.example.service.AccountService;

public class TestAppConfig {
 public static void main(String[] args) {
  ConfigurableApplicationContext context=
   new AnnotationConfigApplicationContext(AppConfig.class);
  AccountService accountSrv=
    (AccountService) context.getBean("accountsrv");
  Account account1= context.getBean("acc1",Account.class);
  Account account2= context.getBean("acc2",Account.class);
  accountSrv.transferMoney(account1, account2, 102500000);
  context.close();    
 }
}

Uygulamayı çalıştığımızda aşağıdaki gibi ekran görüntüsü oluşmaktadır:

Feb 10, 2014 12:19:15 AM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@33d4f6b4: startup date [Mon Feb 10 00:19:15 EET 2014]; root of context hierarchy
Feb 10, 2014 12:19:15 AM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@4a2c1b1c: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,appConfig,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor,accountsrv,largeAmountTransferListener,acc1,acc2]; root of factory hierarchy
Large amount transfer has occured: 1.025E8 at 2014-02-10 00:19:15.99
Feb 10, 2014 12:19:15 AM org.springframework.context.support.AbstractApplicationContext doClose
INFO: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@33d4f6b4: startup date [Mon Feb 10 00:19:15 EET 2014]; root of context hierarchy
Feb 10, 2014 12:19:15 AM org.springframework.beans.factory.support.DefaultSingletonBeanRegistry destroySingletons
INFO: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@4a2c1b1c: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,appConfig,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor,accountsrv,largeAmountTransferListener,acc1,acc2]; root of factory hierarchy

Uygulamanın kaynak koduna bu adresten maven projesi olarak erişebilirsiniz.

No comments:

Post a Comment