Category Archives: Yazılım ve Sistem Mühendisliği

Yazılım ve Sistem Mühendisliği

Observer Tasarım Kalıbı ve Olay Güdümlü Programlama

Üzerinde çalıştığım bir projeye kısa bir çay molası vermişken sizlerle çok sevdiğim ve şu an üzerinde çalıştığım projede de kullandığım gözlemci (observer) tasarım kalıbı ve olay güdümlü programlama konularndan kısaca bahsetmek istiyorum.

87628254
Olay güdümlü programlama, iç veya dış etkenlerle meydana gelen olaylar doğrultusunda program akışının değiştirilmesi gerektiği durumlarda kullanılan bir tekniktir. Bir e-ticaret sitesinde ziyaretcinin herhangibir nedenle alışverişi tamamlayamaması durumunda belirli bir kullanıcı gurubunun haberdar edilmesi, herhangibir servisle iletişim sorunu olması durumunda aksiyon alınması gibi durumlar, olay güdümlü programlama gerektirir.

Observer
Observer tasarım kalıbı ise birbirleriyle bire-çok ilişki içindeki nesnelerden herhangibirinde değişiklik olması durumunda bir merkez üzerinden diğer nesnelerin haberdar edilmesi gerektiği durumlarda kullanılır ve olay güdümlü programlama için biçilmiş kaftandır :)

Dilerseniz gelin hemen birlikte gerçek hayattan bir örnek inceleylim.

Bir elektronik ticaret şirketinde çalışıyoruz. Saat başı zamanlanmış bir görevle gelen siparişleri lojistik şirketine iletiyoruz. Ancak siparişlerin herhangibir nedenle iletilememesi durumunda 1 dk bekleyip tekrar deneme gerçekleştirmek istiyoruz.

Kod örneğine geçmeden önce gözlemci tasarım kalıbı ve olay güdümlü programlama arasındaki ilişki konusunda kısa bir bilgi vermekte yarar var. Observer tasarım desenini kullanarak yazılımımızın akışıını olaylarla değiştirebilmek için olayın gerçekleştiği yeri merkez, olayı dinleyen bölümü observer olarak düşünmeliyiz.

Aşağıdaki gibi bir lojistik arayüzümüz var. Tüm lojistik implementasyonlarımız bu arayüzü implement eder.

interface ILogistic
{
	/**
	 * @param string $articleCode
	 * @param string $refNumber
	 * @return bool
	 */
	public function send($articleCode, $refNumber);
	
	/**
	 * @param string $refNum
	 * @return string
	 */
	public function getStatus($refNum);
}

Basit ve anlaşılır olması açısından herbir gözlemcinin tek bir olay içerdiğini varsayıyoruz. Aşağıda gözlemci sınıflarımızın implement edeceği arayüz kodu yeralıyor.

interface IObserver
{
	/**
	 * @param Event $event
	 */
	public function invoke(Event $event);
}

Olayın gerçekleşeceği bölümün implement edeceği IObservable arayüzünü kodlayalım.

interface IObservable
{
	/**
	 * @param IObserver $observer
	 * @param string $eventName
	 */
	public function attach(IObserver $observer, $eventName);
	
	/**
	 * @param Event $event
	 */
	public function fireEvent(Event $event);
}

Olay gerçekleştiğinde olayla ilgili bilgileri gözlemciye (observer) taşıyacak olay sınıfımızı kodlayalım.

class Event
{
	/**
	 * @var string
	 */
	private $_event_type;
	
	/**
	 * @var IObservable
	 */
	private $_source;
	
	/**
	 * @var string
	 */
	private $_last_called_method;
	
	/**
	 * @var array
	 */
	private $_arguments;
	
	/**
	 * @param string $eventName
	 * @param IObservable $source
	 * @param string $lastCalledMethod
	 * @param array $arguments
	 */
	public function __construct($eventName,  IObservable $source, $lastCalledMethod, array $arguments)
	{
		$this->_event_name = $eventName;
		$this->_source = $source;
		$this->_last_called_method = $lastCalledMethod;
		$this->_arguments = $arguments;
	}
	
	/**
	 * @return string
	 */
	public function getEventName()
	{
		return $this->_event_type;
	}
	
	/**
	 * @return IObservable
	 */
	public function getSource()
	{
		return $this->_source;
	}
	
	/**
	 * @return string
	 */
	public function getLastCalledMethod()
	{
		return $this->_last_called_method;
	}
	
	/**
	 * @return array
	 */
	public function getArguments()
	{
		return $this->_arguments;
	}
}

Lojistik implementasyonlarımızın devralacağı soyut sınıfımızı kodlayalım.

abstract class LogisticAbstract
{	
	/**
	 * @param string $articleCode
	 * @param string $refNum
	 * @return array
	 */
	abstract protected function _getSendRequest($articleCode, $refNum);
	
	/**
	 * @param string $refNum
	 * @return array
	 */
	abstract protected function _getStatusRequest($refNum);
	
	/**
	 * @param string $apiResponse
	 * @return bool
	 */
	abstract protected function _parseSendRequest($apiResponse);
	
	/**
	 * @param string $apiResponse
	 * @return string
	 */
	abstract protected function _parseStatusRequest($apiResponse);
	
	/**
	 * @param string $url
	 * @param array $data
	 * @throws Exception
	 * @return string
	 */
	protected function _sendRequest($url, array $data)
	{
		$postData = http_build_query($data);
		$ch = curl_init();
		$options = array(CURLOPT_URL => $url,
					    CURLOPT_RETURNTRANSFER => true,
					    CURLOPT_POST 	   => true,
					    CURLOPT_POSTFIELDS 	   => $postData);
		curl_setopt_array($ch, $options);
		$response = curl_exec($ch);
	    if($error = curl_errno($ch)) {
	    	throw new Exception('Error occurred while sending data to remote server. Detail:' . $error, '');
	    }
	    return $response;
	}
}

Lojistik şirketimizin adı örnek lojistik olsun.

class OrnekLojistik extends LogisticAbstract implements ILogistic, IObservable
{
	/**
	 * @var array
	 */
	private $_observers = array();
	
	/**
	 * @param string $articleCode
	 * @param string $refNumber
	 * @return array
	 */
	protected function _getSendRequest($articleCode, $refNumber)
	{
		$xml = "
				
				  
				  send
				  {$articleCode}
				  {$refNumber}
				  
				";
		return array('data' => $xml);
	}
	
	/**
	 * @param string $refNum
	 * @return array
	 */
	protected function _getStatusRequest($refNum)
	{
		//...
	}
	
	/**
	 * @param string $apiResponse
	 */
	protected function _parseSendRequest($apiResponse)
	{
		try {
			$xml = new SimpleXMLElement($xml);
			return $xml->Response->Status == 'OK';		
		} catch(Exception $e) {
			throw new Exception('Unexpected Response. Response message:' . $apiResponse);
		}
	}
	
	/**
	 * @param string $apiResponse
	*/
	protected function _parseStatusRequest($apiResponse)
	{
		//...
	}
	
	/**
	 * @param string $articleCode
	 * @param string $refNumber
	 */
	public function send($articleCode, $refNumber)
	{
		try {
			$request = $this->_getSendRequest($articleCode, $refNumber);
			$apiResponse = $this->_sendRequest('http://ws.orneklojistik.com/OrnekWS.php', $request);
			$response = $this->_parseSendRequest($apiResponse);
			if(! $response ) {
				$this->fireEvent(new Event('shipping_failed', $this, __METHOD__, func_get_args() ));
			}
			return $response;
		} catch(Exception $e) {
			$this->fireEvent(new Event('shipping_failed', $this, __METHOD__, func_get_args() ));
                        return false;
		}
	}
	
	/**
	 * @param string $refNum
	*/
	public function getStatus($refNum)
	{
		//...		
	}
	
	/**
	 * (non-PHPdoc)
	 * @see IObservable::attach()
	 */
	public function attach(IObserver $observer, $eventName)
	{
		if( ! isset( $this->_observers[$eventName] ) ) {
			$this->_observers[$eventName] = array();
		}
		$this->_observers[$eventName][] = $observer;
	}
	
	/**
	 * (non-PHPdoc)
	 * @see IObservable::fireEvent()
	 */
	public function fireEvent(Event $event)
	{
		if( isset( $this->_observers[$event->getEventName() ] ) ) {
			/* @var $event IObserver  */
			foreach ($this->_observers[$event->getEventName()] as $observer) {
				$observer->invoke($event);
			}
		}
	}
}

Lojistik şirketine gönderim sırasında sorun yaşamamız durumunda çalışacak gözlemci sınıfını kodlayalım.

class OrnekLojistik_Observer_OnFail implements IObserver
{
	/**
	 * @var bool
	 */
	private $_tried = false;

	/**
	 * (non-PHPdoc)
	 * @see IObserver::invoke()
	 */
	public function invoke(Event $event)
	{
		if( ! $this->_tried ) {
			sleep(60);
			call_user_method($event->getLastCalledMethod(), $event->getSource(), $event->getArguments());
		}	
	}
}

Geldik zamanlanmış görevimize. Buyrun size zamanlanmış görev…

class SendLogisticCommand implements ICommand
{
	/**
	 * @param array $args
	 */
	public function run($args)
	{
		$products = array('ABC123', 'DEF234', 'GHI123');
		
		$logistic = new OrnekLojistik();
		$observer = new OrnekLojistik_Observer_OnFail();
		$logistic->attach($observer, 'shipping_failed');
		
		foreach($products as $product) {
			$logistic->send($product, time() );
		}
	}
}

Biraz uzun oldu sanırım :) Hızlıca hazırladığım için ufak tefek hiyerarşi sıkıntıları olabilir ancak genel anlamda sizlere fikir verebileceğini ümit ediyorum.

Yazılım ve Sistem Mühendisliği

Refactoring üstüne

Bu konu üstüne bir giriş yazısı yazmak istedim ama üstad bana söyleyecek söz bırakmamış. Buyrun önce bunu okuyun sonra da spagetti yazılmış örnek bir kod üzerinde birlikte basitce refactoring gerçekleştirelim.

Aslında konunun özü gayet basit:
“Any fool can write code that a computer can understand.Good programmers write code that humans can understand.” (Martin Fowler)

Her apta bilgisayarın anlayabileceği kodu yazabilir. İyi programcılar insanların anlayabileceği kodu yazarlar.

Kodunuz bu standarda uyana kadar refactor etmeye devam edin. :)

if($_POST['bank_id'] == 1) {
  $mBankXml = 'data=
	
	10001
	904567564
	PROVAUT
	' . $_POST['orderid'] . '
        ' . $_POST['amount'] . '
	
	' . $_POST['ccno'] .'
	' . $_POST['expdate'] . '
	' . $_POST['scode'] . '
	
	';
	$curl = curl_init();
	$options = array(
		    CURLOPT_URL             => 'http://vpos.abank.com',
		    CURLOPT_POST            => true,
		    CURLOPT_RETURNTRANSFER  => true,
		    CURLOPT_SSL_VERIFYPEER  => false,
		    CURLOPT_SSL_VERIFYHOST  => true,
		    CURLOPT_HEADER          => false,
		    CURLOPT_POSTFIELDS      => $mBankXml
		);
	curl_setopt_array($curl, $options);
	$rd = curl_exec($curl);
	curl_close($curl);
        $xmlObject = simplexml_load_string($rd);
	if($xmlObject->response == 'approved') {
 		$sql = 'UPDATE pos_transactions SET status=1 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        } else {
		$sql = 'UPDATE pos_transactions SET status=0 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        }
} elseif($_POST['bank_id'] == 2) {
	$mBankXml = 'data=
	
	10001
	904567564
	PROVAUT
	' . $_POST['orderid'] . '
        ' . $_POST['amount'] . '
	
	' . $_POST['ccno'] .'
	' . $_POST['expdate'] . '
	' . $_POST['scode'] . '
	
	';
	$curl = curl_init();
	$options = array(
		    CURLOPT_URL             => 'http://vpos.bbbank.com',
		    CURLOPT_POST            => true,
		    CURLOPT_RETURNTRANSFER  => true,
		    CURLOPT_SSL_VERIFYPEER  => false,
		    CURLOPT_SSL_VERIFYHOST  => true,
		    CURLOPT_HEADER          => false,
		    CURLOPT_POSTFIELDS      => $mBankXml
		);
	curl_setopt_array($curl, $options);
	$rd = curl_exec($curl);
	curl_close($curl);
        $xmlObject = simplexml_load_string($rd);
	if($xmlObject->response == 'approved') {
 		$sql = 'UPDATE pos_transactions SET status=1 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        } else {
		$sql = 'UPDATE pos_transactions SET status=0 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        }
} elseif($_POST['bank_id'] == 3) {
	$mBankXml = 'data=
	
	10001
	904567564
	PROVAUT
	' . $_POST['orderid'] . '
        ' . $_POST['amount'] . '
	
	' . $_POST['ccno'] .'
	' . $_POST['expdate'] . '
	' . $_POST['scode'] . '
	
	';
	$curl = curl_init();
	$options = array(
		    CURLOPT_URL             => 'http://vpos.cbank.com',
		    CURLOPT_POST            => true,
		    CURLOPT_RETURNTRANSFER  => true,
		    CURLOPT_SSL_VERIFYPEER  => false,
		    CURLOPT_SSL_VERIFYHOST  => true,
		    CURLOPT_HEADER          => false,
		    CURLOPT_POSTFIELDS      => $mBankXml
		);
	curl_setopt_array($curl, $options);
	$rd = curl_exec($curl);
	curl_close($curl);
        $xmlObject = simplexml_load_string($rd);
	if($xmlObject->response == 'approved') {
 		$sql = 'UPDATE pos_transactions SET status=1 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        } else {
		$sql = 'UPDATE pos_transactions SET status=0 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        }
} elseif($_POST['bank_id'] == 4) {
	$mBankXml = 'data=
	
	10001
	904567564
	PROVAUT
	' . $_POST['orderid'] . '
        ' . $_POST['amount'] . '
	
	' . $_POST['ccno'] .'
	' . $_POST['expdate'] . '
	' . $_POST['scode'] . '
	
	';
	$curl = curl_init();
	$options = array(
		    CURLOPT_URL             => 'http://vpos.dbank.com',
		    CURLOPT_POST            => true,
		    CURLOPT_RETURNTRANSFER  => true,
		    CURLOPT_SSL_VERIFYPEER  => false,
		    CURLOPT_SSL_VERIFYHOST  => true,
		    CURLOPT_HEADER          => false,
		    CURLOPT_POSTFIELDS      => $mBankXml
		);
	curl_setopt_array($curl, $options);
	$rd = curl_exec($curl);
	curl_close($curl);
        $xmlObject = simplexml_load_string($rd);
	if($xmlObject->response == 'approved') {
 		$sql = 'UPDATE pos_transactions SET status=1 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        } else {
		$sql = 'UPDATE pos_transactions SET status=0 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        }
}

Ürkütücü dğeil mi ? Ama reel hayatta malesef bu ve daha da kötüleri de mevcut. Şimdi dilerseniz gelin bu kodları adım adım birlikte refactor edelim.

Öncelikle kodumuzu baştan uca incelediğimizde sayfaya post edilen banka kimliğine göre kodun belirli bir banka api sine veri göndermesi ve sonrasında isteğin sonucu başarılıysa veya başarısızsa veritabanında bir kaydın güncellenmesi gerekiyor.

Her apiye bir http isteği gönderdiğimize göre ilk olarak http isteği yapan bölümü fonksiyon haline getirelim.

/**
  Bu fonksiyon belirli bir url e 
  belirtilen veriyi post eder.

  @param string $url
  @param string $data
  @return string
 */
function sendHttpRequest($url, $data)
{
  $curl = curl_init();
  $options = array(
      CURLOPT_URL             => $url,
      CURLOPT_POST            => true,            
      CURLOPT_RETURNTRANSFER  => true,
      CURLOPT_SSL_VERIFYPEER  => false,   
      CURLOPT_SSL_VERIFYHOST  => true,            
      CURLOPT_HEADER          => false,                   
      CURLOPT_POSTFIELDS      => $data
      );                                                              
  curl_setopt_array($curl, $options);
  $response = curl_exec($curl);
  curl_close($curl);
  return $response
}

Bu değişiklikten sonra kodumuz aşağıdaki hali aldı.

if($_POST['bank_id'] == 1) {
  $mBankXml = 'data=
	
	10001
	904567564
	PROVAUT
	' . $_POST['orderid'] . '
        ' . $_POST['amount'] . '
	
	' . $_POST['ccno'] .'
	' . $_POST['expdate'] . '
	' . $_POST['scode'] . '
	
	';
	$apiResponse = sendHttpRequest('http://vpos.abank.com', $mBankXml);
        $xmlObject = simplexml_load_string($apiResponse);
	if($xmlObject->response == 'approved') {
 		$sql = 'UPDATE pos_transactions SET status=1 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        } else {
		$sql = 'UPDATE pos_transactions SET status=0 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        }
} elseif($_POST['bank_id'] == 2) {
	$mBankXml = 'data=
	
	10001
	904567564
	PROVAUT
	' . $_POST['orderid'] . '
        ' . $_POST['amount'] . '
	
	' . $_POST['ccno'] .'
	' . $_POST['expdate'] . '
	' . $_POST['scode'] . '
	
	';
	$apiResponse = sendHttpRequest('http://vpos.bbank.com', $mBankXml);
        $xmlObject = simplexml_load_string($apiResponse);
	if($xmlObject->response == 'approved') {
 		$sql = 'UPDATE pos_transactions SET status=1 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        } else {
		$sql = 'UPDATE pos_transactions SET status=0 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        }
} elseif($_POST['bank_id'] == 3) {
	$mBankXml = 'data=
	
	10001
	904567564
	PROVAUT
	' . $_POST['orderid'] . '
        ' . $_POST['amount'] . '
	
	' . $_POST['ccno'] .'
	' . $_POST['expdate'] . '
	' . $_POST['scode'] . '
	
	';
	$apiResponse = sendHttpRequest('http://vpos.cbank.com', $mBankXml);
        $xmlObject = simplexml_load_string($apiResponse);
	if($xmlObject->response == 'approved') {
 		$sql = 'UPDATE pos_transactions SET status=1 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        } else {
		$sql = 'UPDATE pos_transactions SET status=0 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        }
} elseif($_POST['bank_id'] == 4) {
	$mBankXml = 'data=
	
	10001
	904567564
	PROVAUT
	' . $_POST['orderid'] . '
        ' . $_POST['amount'] . '
	
	' . $_POST['ccno'] .'
	' . $_POST['expdate'] . '
	' . $_POST['scode'] . '
	
	';
	$apiResponse = sendHttpRequest('http://vpos.dbank.com', $mBankXml);
        $xmlObject = simplexml_load_string($apiResponse);
	if($xmlObject->response == 'approved') {
 		$sql = 'UPDATE pos_transactions SET status=1 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        } else {
		$sql = 'UPDATE pos_transactions SET status=0 WHERE sessionid=' . $_SESSION['id'];
		mysql_query($sql);
        }
}

Farkındayım kod hala bok gibi görünüyor. Şimdi de transaction durumunu güncelleyen veritabanı sorgusu için küçük bir fonksiyon yazalım.

/**
  Bu fonksiyon pos transaction ının 
  durumunu günceller.

  @param integer transactionId
  @param int status
 */
function setTransactionStatus($transactionId, $status)
{
  $transactionId  = (int) $transactionId;
  $status         = (int) $status;
  $sqlText        = "UPDATE pos_transactions SET status={$status} WHERE sessionid={$transactionId}";
  mysql_query($sqlText);
}

Bu fonksiyondan sonra kodumuz aşağıdaki hali alacak.

if($_POST['bank_id'] == 1) {
  $mBankXml = 'data=
	
	10001
	904567564
	PROVAUT
	' . $_POST['orderid'] . '
        ' . $_POST['amount'] . '
	
	' . $_POST['ccno'] .'
	' . $_POST['expdate'] . '
	' . $_POST['scode'] . '
	
	';
	$apiResponse = sendHttpRequest('http://vpos.abank.com', $mBankXml);
        $xmlObject = simplexml_load_string($apiResponse);
	if($xmlObject->response == 'approved') {
 		setTransactionStatus($_SESSION['id'], 1);
        } else {
		setTransactionStatus($_SESSION['id'], 0);
        }
} elseif($_POST['bank_id'] == 2) {
	$mBankXml = 'data=
	
	10001
	904567564
	PROVAUT
	' . $_POST['orderid'] . '
        ' . $_POST['amount'] . '
	
	' . $_POST['ccno'] .'
	' . $_POST['expdate'] . '
	' . $_POST['scode'] . '
	
	';
	$apiResponse = sendHttpRequest('http://vpos.bbank.com', $mBankXml);
        $xmlObject = simplexml_load_string($apiResponse);
	if($xmlObject->response == 'approved') {
 		setTransactionStatus($_SESSION['id'], 1);
        } else {
		setTransactionStatus($_SESSION['id'], 0);
        }
} elseif($_POST['bank_id'] == 3) {
	$mBankXml = 'data=
	
	10001
	904567564
	PROVAUT
	' . $_POST['orderid'] . '
        ' . $_POST['amount'] . '
	
	' . $_POST['ccno'] .'
	' . $_POST['expdate'] . '
	' . $_POST['scode'] . '
	
	';
	$apiResponse = sendHttpRequest('http://vpos.cbank.com', $mBankXml);
        $xmlObject = simplexml_load_string($apiResponse);
	if($xmlObject->response == 'approved') {
 		setTransactionStatus($_SESSION['id'], 1);
        } else {
		setTransactionStatus($_SESSION['id'], 0);
        }
} elseif($_POST['bank_id'] == 4) {
	$mBankXml = 'data=
	
	10001
	904567564
	PROVAUT
	' . $_POST['orderid'] . '
        ' . $_POST['amount'] . '
	
	' . $_POST['ccno'] .'
	' . $_POST['expdate'] . '
	' . $_POST['scode'] . '
	
	';
	$apiResponse = sendHttpRequest('http://vpos.dbank.com', $mBankXml);
        $xmlObject = simplexml_load_string($apiResponse);
	if($xmlObject->response == 'approved') {
 		setTransactionStatus($_SESSION['id'], 1);
        } else {
		setTransactionStatus($_SESSION['id'], 0);
        }
}

Evet kod çöplüğümüz ufaktan birşeye benzemeye başlıyor. Şimdi de API lere gönderdiğimiz istekleri fonksiyonlaştıralım.

/**
  ABank icin odeme istegi gonderir.
  @param string $orderId
  @param float $amount
  @param string $cardNo
  @param string $cvc2
  @param string $expDate
  @return boolean
 */
function sendABankPaymentRequest($orderId, $amount, $cardNo, $cvc2, $expDate)
{
  $strXml = 'data=
    
    10001
    904567564
    PROVAUT
    ' . $orderId . '
    ' . $amount . '
    
    ' . $cardNo .'
    ' . $expDate . '
    ' . $cvc2 . '
    
    ';
  $apiResponse  = sendHttpRequest('http://vpos.abank.com', $strXml);
  $xmlObject    = simplexml_load_string($apiResponse);
  return ($xmlObject->response == 'approved') ? true : false;
}

/**
  BBank icin odeme istegi gonderir.
  @param string $orderId
  @param float $amount
  @param string $cardNo
  @param string $cvc2
  @param string $expDate
  @return boolean
 */
function sendBBankPaymentRequest($orderId, $amount, $cardNo, $cvc2, $expDate)
{
  $strXml = 'data=
    
    10001
    904567564
    PROVAUT
    ' . $orderId . '
    ' . $amount . '
    
    ' . $cardNo .'
    ' . $expDate . '
    ' . $cvc2 . '
    
    ';
  $apiResponse  = sendHttpRequest('http://vpos.bbank.com', $strXml);
  $xmlObject    = simplexml_load_string($apiResponse);
  return ($xmlObject->response == 'approved') ? true : false;
}

/**
  CBank icin odeme istegi gonderir.
  @param string $orderId
  @param float $amount
  @param string $cardNo
  @param string $cvc2
  @param string $expDate
  @return boolean
 */
function sendCBankPaymentRequest($orderId, $amount, $cardNo, $cvc2, $expDate)
{
  $strXml = 'data=
    
    10001
    904567564
    PROVAUT
    ' . $orderId . '
    ' . $amount . '
    
    ' . $cardNo .'
    ' . $expDate . '
    ' . $cvc2 . '
    
    ';
  $apiResponse  = sendHttpRequest('http://vpos.bbank.com', $strXml);
  $xmlObject    = simplexml_load_string($apiResponse);
  return ($xmlObject->response == 'approved') ? true : false;
}

/**
  DBank icin odeme istegi gonderir.
  @param string $orderId
  @param float $amount
  @param string $cardNo
  @param string $cvc2
  @param string $expDate
  @return boolean
 */
function sendDBankPaymentRequest($orderId, $amount, $cardNo, $cvc2, $expDate)
{
  $strXml = 'data=
    
    10001
    904567564
    PROVAUT
    ' . $orderId . '
    ' . $amount . '
    
    ' . $cardNo .'
    ' . $expDate . '
    ' . $cvc2 . '
    
    ';
  $apiResponse  = sendHttpRequest('http://vpos.bbank.com', $strXml);
  $xmlObject    = simplexml_load_string($apiResponse);
  return ($xmlObject->response == 'approved') ? true : false;
}

Ve… kodumuzun yeni hali…

if($_POST['bank_id'] == 1) {
  $bankResponse = sendABankPaymentRequest($_POST['orderId'], 
                                          $_POST['amount'], 
                                          $_POST['ccno'], 
                                          $_POST['cvc2'], 
                                          $_POST['expdate']);
  if($bankResponse) {
    setTransactionStatus($_SESSION['id'], 1);
  } else {
    setTransactionStatus($_SESSION['id'], 0);
  }
} elseif($_POST['bank_id'] == 2) {
  $bankResponse = sendBBankPaymentRequest($_POST['orderId'], 
                                          $_POST['amount'], 
                                          $_POST['ccno'], 
                                          $_POST['cvc2'], 
                                          $_POST['expdate']);
  if($bankResponse) {
    setTransactionStatus($_SESSION['id'], 1);
  } else {
    setTransactionStatus($_SESSION['id'], 0);
  }
} elseif($_POST['bank_id'] == 3) {
  $bankResponse = sendCBankPaymentRequest($_POST['orderId'], 
                                          $_POST['amount'], 
                                          $_POST['ccno'], 
                                          $_POST['cvc2'], 
                                          $_POST['expdate']);
  if($bankResponse) {
    setTransactionStatus($_SESSION['id'], 1);
  } else {
    setTransactionStatus($_SESSION['id'], 0);
  }
} elseif($_POST['bank_id'] == 4) {
  $bankResponse = sendDBankPaymentRequest($_POST['orderId'], 
                                          $_POST['amount'], 
                                          $_POST['ccno'], 
                                          $_POST['cvc2'], 
                                          $_POST['expdate']);
  if($bankResponse) {
    setTransactionStatus($_SESSION['id'], 1);
  } else {
    setTransactionStatus($_SESSION['id'], 0);
  }
}

Eskiye oranla kodumuz oldukça kısa ve okunaklı oldu. Ancak yine de Aynı işi yapan blokların tekrar etmesi biraz can sıkıcı. Dilerseniz gelin şimdi de bankalara istek gönderen fonksiyonları tekilleştirelim ve sonuca göre bir kerede pos transaction durumumuzu güncelleyelim.

/**
  kimliği belirtilen bankaya ödeme isteği gönderir.

  @param int bankId
  @param string $orderId
  @param float $amount
  @param string $cardNo
  @param string $cvc2
  @param string $expDate
  @return boolean
 */

function sendPaymentRequest($bankId, $orderId, $amount, $cardNo, $cvc2, $expDate)
{
  $functionMap = array(
        1 => 'sendABankPaymentRequest',
        2 => 'sendBBankPaymentRequest',
        3 => 'sendCBankPaymentRequest',
        4 => 'sendDBankPaymentRequest'
      );

  if(!in_array($bankId, $functionMap)) {
    return false;
  }
  
  $function = $functionMap[$bankId];

  return $$function($_POST['orderId'],
      $_POST['amount'],
      $_POST['ccno'],
      $_POST['cvc2'],
      $_POST['expdate']);
}

Bu geliştirmeden sonra kodumuz son olarak aşağıdaki hali alacaktır.

$bankResponse = sendPaymentRequest($_POST['bank_id'],
    $_POST['orderId'], 
    $_POST['amount'], 
    $_POST['ccno'], 
    $_POST['cvc2'], 
    $_POST['expdate']);
if($bankResponse) {
  setTransactionStatus($_SESSION['id'], 1);
} else {
  setTransactionStatus($_SESSION['id'], 0);
}

Berker’in comment’inden sonra gelen edit:

$bankResponse = sendPaymentRequest($_POST['bank_id'],
    $_POST['orderId'], 
    $_POST['amount'], 
    $_POST['ccno'], 
    $_POST['cvc2'], 
    $_POST['expdate']);
setTransactionStatus($_SESSION['id'], (int) $bankResponse);

Göktuğ’nun comment’inden sonra sendPaymentRequest() fonksiyonuna gelen edit:

/**
  kimliği belirtilen bankaya ödeme isteği gönderir.

  @param int bankId
  @param string $orderId
  @param float $amount
  @param string $cardNo
  @param string $cvc2
  @param string $expDate
  @return boolean
 */

function sendPaymentRequest($bankId, $orderId, $amount, $cardNo, $cvc2, $expDate)
{
  $functionMap = array(
        1 => 'sendABankPaymentRequest',
        2 => 'sendBBankPaymentRequest',
        3 => 'sendCBankPaymentRequest',
        4 => 'sendDBankPaymentRequest'
      );

  if(!in_array($bankId, $functionMap)) {
    return false;
  }

  return $functionMap[bankId]($orderId,
      $amount,
      $cardNo,
      $cvc2,
      $expDate);
}
Yazılım ve Sistem Mühendisliği

Geliştirme Metodolojisi Finansal Bir Karardır

Yıllar geçse de, yeni teknolojiler çıksa da PHP gibi serbest yazım stillerine izin veren diller olduğu sürece MVC öcü mü ? oop mi spagetti mi gibi tartışmalar daha uzun yıllar devam edecektir…

***

İşin özü, projenizin programlama stilini belirlemek veya değiştirmek, tamamen finansal bir karardır. Aynen vadeli veya peşin para ile alışveriş yapmak gibi.

Prosedürel programlama kolaydır, fonksiyon kütüphaneleri ve kategorize edilmiş klasör yapılarıyla sizden bekleneni hızlıca üreterek işvereninizi ve yöneticilerinizi kolayca memnun etmenizi sağlar. Bazı durumlarda mvc frameworkler ile geliştirilen projelere göre daha az kaynak tükettiği ve daha hızlı aplikasyonların geliştirilmesine elverişli olduğu konusu gerçektir. Ancak kalabalıklaşan ve projenin geneli hakkında bilgisi olmayan ekiplerin proje üzerinde çalışması ve projenin yaşlanması gibi faktörler kod tekrarına; çok basit ancak projenin geneline etki etmesi beklenen geliştirmeler bazen günler sürerek gereksiz geliştirici kaynağı tüketimine neden olabilir.

MVC frameworkler, umumiyetle nesne yönelimli programlama bilgisi gerektiren yapılardır. Gücü yada hızı kullandığınız framework un dispatch cycle ına göre değişkenlik gösterir. Deneyimli developer lar ve tasarım bilgisi gerektirir. Yapılacak her geliştirme programatik ortamda doğru modellenmeli ve dökümante edilmelidir. Concurrent kullanımın artması gibi durumlarda sunucunuz üzerindeki yükün ciddi manada artacağı konusu bir hakikattir. Kısa vadede bu ve benzeri durumlar gözünüzü korkutsada uzun vadede mvc framework lerin sağlayacağı yararların tartışılmazlığı kesindir.

MVC frameworklerin kullanımıyla ilgili olarak başta bahsettiğim concurrent kullanıcı sayısından doğan yük, doğru ölçeklenmiş, dağıtık bir yapıda rahatlıkla karşılanabilir. Ayrıca mvc hiyararşisi, basit manada tüm business logic in model katmanında, arayüz içeriğinin view katmanında ve bu iki katman arasındaki iletişimi sağlayan yapının controller katmanında olmasını gerektirir. Böylelikle sistemin fonksiyonel bölümünü görsel bölümden izole etmiş olursunuz. Projenin geneline etki etmesi beklenen geliştirmeler, mvc frameworklerin sunduğu plugin gibi yapılarla son derece basit ve kısa sürelerde gerçekleştirilebilir(Zend framework’ün controller plugin leri bu konuya iyi bir örnektir.). DbTable ve ORM gibi, frameworklerle birlikte gelen yada uyumlu çalışabilen veri erişimi sistemleri, injection gibi tehlikeli durumlardan sizi koruyarak güvenli sistemler geliştirmenize olanak sağlar. Son olarak her framework kendi kod standartlarıyla birlikte gelir. Kurallara uygun yazılan, düzgün modellenmiş ve dokümante edilmiş sistemler, minimum kod tekrarıyla temiz ve anlaşılır kod yazmanıza olanak sağlar.

***

Ve… emin olun sizden alelacele iş bekleyerek, sizi sistemi çöp haline getirmeye zorlayan her işveren veya yönetici, uzayan development süreleri ve büyüyen maliyetlerden bezerek “Framework diye birşey varmış…” diyecek ve konuyu gündemine alacaktır.

Yazılım ve Sistem Mühendisliği

Tasarım Candır

Epeydir teknik konularda sağda solda blog yazıları yazıyorum, iltraf etmeliyim bugüne kadar hiçbir yazım için Bu işin bir pattern i olmalı! kadar olumlu tepki almamıştım, çok şaşırdım.

Mesleki kariyerim için tehlikeli; hislere tercüman olma noktasında başarılı bir yazı oldu sanırım :)

***

Genel geliştirici kafasıdır, önümüze bir iş gelir daha işin ne olduğunu bile doğru dürüst anlamadan bodoslama kod yazmaya girişiz. Daha kodlama aşamasında aklımıza farklı metodolojiler gelir, eski yazdıklarımızı refaktör eder yola devam ederiz, sonra farkederiz ki kazın ayağı öyle değilmiş, yazdığımız kod ne isteneni, ne de ilişkili olduğu başka işlerden doğan ekstra case leri karşılıyor… Tohumuna para mı saydık. Oturur bir daha yazarız… Gider biryerlerden birşeyler okuruz, has… lan bu böyle de yapılabiliyormuş der yine yazarız. Araya başka iş girer, zaman geçer neyi neden yaptığımızı unutur yine başa döneriz….

Tohumuna para mı saydım dediğiniz şey, kısa vadede zaman kaybı, uzun vadede business gözünde “IT ye giden iş gelmiyor”, yöneticinizin gözünde “Mehmetin verimi düşük günlerdir aynı işin üzerinde çalışıyor”, arkadaşlarınızın gözünde “lan bu mehmet de yalanmış” şeklinde oluşan imaj, türlü prestij kaybı ve hatta daha kötüsü işinizden kovularak para kaybı olarak size geri dönecektir.

Tabi bir de herhangibirşey karalamadan giriştiğiniz bu işin, ön görmediğiniz side effectlerinden doğan reopen ticketler, sizi kovulma level ine birkaç adım daha yaklaştıracaktır.

***

Hazır olun! Mesaj geliyor…
Mimar gibi tasarlayın, müteahhit gibi kodlayın.

Sizden istenen şeye daima bir proje edasıyla yaklaşın. İşi, büyüklüğüne göre mümkün olduğunca kendi başına çalışabilen küçük parçalara ayırın ve herbir parçanın kusursuz çalışacağından emin olun. (Tabi kağıt üstünde mümkün olabilecek kadar). Çok fazla zaman kaybettirmeyecek şekilde mükemmeli yakalayana kadar tekrarlayın. Tıpkı bir gökdeleni incelikle tasarlayan bir mimar gibi.

Daha sonra tasarladığınız bu yapıyı bilgisayarınızın başına oturup kodlayarak hayata geçirin. Kodlama sırasında zamanı para olarak nitelendirin ve paradan kırpmaya çalışan laz müteahhit kafasıyla kodlama yapın. Tasarımınızın doğruluğundan eminseniz yolda aklınıza gelen fikirleri sonraki faza bırakın. Kişisel fikrimi soracak olursanız yolda akla gelen fikirleri doğrudan ignore edin. Müşteriniz ekranda gördüğüyle, yöneticiniz işin ne kadar zamanda ne kadar az hatayla tamamlandığına bakar, program koduna ise yalnızca siz veya belki birkaç takım arkadaşınız.