ODATA Web Service Uygulaması

ODATA, Destinations, CRUD İşlemler

Herkese merhaba. Önceki yazımda sizlerle beraber Fiori’ye küçük bir giriş yapmıştık ve herhangi bir web servise bağlamadan küçük örnekler göstermiştik. Bugün ise işi bir adım ileri götürerek kendi ODATA servisimizi oluşturup CRUD(Create, Read, Update, Delete) işelemler yapabileceğimiz temel bir örnek ile konumuzu pekiştireceğiz.

ODATA SERVICES

SAP’nin farklı istemciler ile veri alışverişi sağladığı bir servis yapısıdır. Bu veri alışverişini GUI tarafında yazdığımız ABAP kodları ile işlemler yaparak sağlıyoruz. Servisler temel olarak varlıklar(entity), bu varlıkların özellikleri(property) ve bu varlıklara bağlı metotlar’dan oluşur. ODATA servisleri için GUI’de SEGW t-code ile işlemler yapılır. ODATA servisler ile kullanabildiğimiz HTTP request tipleri:

HTTP Request Tipleri

DESTINATIONS

Local Web Ide’de çalışırken eğer bir servise bağlı çalışacaksak, hangi sistem ile çalışıyorsak o sisteme ait destination dosyasını bulundurmamız gereklidir. İşte benim kullandığım örnek destination dosyası:


Bu dosyadaki bilgileri hangi sistemde çalışıyorsak o sistemin bilgileri ile doldurmamız gereklidir. Peki bu bilgileri nasıl öğreniyoruz? GUI’de WEBGUI t-code’unu yazdığımız zaman karşımıza böyle bir pop-up çıkıyor:


URL kısmında ben kendi local sistemimizin sunucusunu yazdım ve bu pop-up’da görüldüğü gibi portu 8000 yazdım. Farklı bir sistemde çalışacağımız zaman URL kısmına yazacağımız sunucu adresi kırmızı kutucuk içine aldığım yerde yazıyor olacak. Bunu öğrenmenin bildiğim en kısa yolu bu 😊 Description ve WebIdeSystem bilgilerini girdikten sonra Client bilgisini de GUI ekranımızın sağ alt köşesindeki bilgiden öğrenebiliriz:


Geri kalan kısımlar da standart alanlardır. Local Web Ide’de bu şekilde manual oluşturulduğu gibi SAP Cockpit’de destination tanımladığımız kısımdan da tanımlayıp export ederek o dosyayı kullanabiliriz.



Yukarıdaki dosya içine yazdığımız bilgileri ilgili alanlara doldurarak destination tanımlayabiliriz. Destination dosyamızı hazırladıktan sonra daha önce Local Web Ide kullanmak bir klasör oluşturmuştuk. Bu klasör içindeki C:\SAPLocalWebIDE\eclipse\config_master\service.destinations\destinations dosya yoluna bunu bu dosyayı yapıştırmalıyız. Artık tanımladığımız destination Projectsde kullanabiliriz. Okumayanlar Local Web Ide Kullanımı ile ilgili yazıma buradan ulaşabilir.Bunun ardından servis ve proje oluşturma adımına geçeceğiz, gerekli detayları proje oluşturma ve geliştirme esnasında vereceğim.


ODATA SERVICE OLUŞTURMA

SEGW t-code’umuza giderek servisimizi oluşturalım ve istenilen bilgileri girip tamamlayalım.


Yapmamız gereken ilk iş bir Entity Type oluşturmak:


Karşımıza aşağıdaki gibi ekran çıkacak. Buradan gerekli isimlendirmeyi yaparak devam edelim. Burada oluşturduğumuz Entity Type yapısını ABAP’daki structure yapısı gibi düşünebiliriz. Yukarıda da belirttiğimiz gibi servisler entity, property ve metotlar’dan oluşur.


Properties’e çift tıkladıktan sonra açılan ekranda bu projede ihtiyaç duyacağımız property'leri tanımladım. Tanımladığım property’ler yukarıda Properties klasörünün altında görünüyor.


Bu tanımlamaları yaparken her property’nin tipini uygun şekilde belirtiyoruz. Fakat tarih ile ilgili kısımda farklı bir ayrıntı var. Default tanımlı hali bizim kullanımımıza uygun olmadığı için editörden kendimize uygun şekilde ayarlamamızı yapmalıyız.


Bunu da tamamladıktan sonra buradaki özellikleri Fiori tarafında kullanabilmek için ListSet’e çift tıklayarak gelen ekranda Addressible seçeneğini işaretlemeliyiz.


Şimdi servisimizi Generate edelim. Bunun için ekranın sol üst köşesindeki kırmızılı icona tıklıyoruz. Tıkladıktan sonra açılan request alanına istediğimiz requesti yazalım ya da yeni request alabilirsiniz.


Tamam dedikten sonra gelen pop-up’da bizim servisimiz için oluşacak bütün class bilgileri gösterilecek.


Bunlara da tamam diyelim. Sıradaki pop-up’larda yine package ve request bilgisi istenecek. Bunlara da kendi package ve request bilgilerimizi girerek ilerleyelim. Bütün işlemler sonrasında hatasız şekilde generate etmiş olmalıyız:


Buraya kadar geldik ama hala servisimizi kullanabilir durumda değiliz. Son olarak /n/iwfnd/maint_service t-code’umua gidelim. Burada sistemdeki standart ve Z’li eklenmiş servisler görünüyor. Fakat burada bir ayrıntı belirtmeliyim. Biz öğrenme aşamamızda local sistemler ile çalıştığımız için bütün işlemleri tek bir GUI ekranı üzerinden yaptık. Fakat farklı bir sistemde bu işlemleri yapmamız gerektiği zaman yani ERP ve GW sistemleri ayrı olduğu durumlarda servis oluşturma işlemi ERP’de bu Hizmet ekleme işlemi GW’de yapılır.


Şimdi servisi oluşturduğumuz sisteme göre sistem takma adı giriyoruz ve karşımıza oluşturulmuş servisler çıkacak:


Kendi servisimizi bulduktan sonra üzerine tıklayalım ve çıkan pop-up’da bir değişiklik yapmadan tamam diyelim.


Karşımıza peşpeşe request pop-up’ları gelecek. Atlamadan hepsini onaylayalım. En son başarılı mesajı almamız gerek. Tamam dedikten sonra servisimizi eklenmiş şekilde göreceğiz.


Servisimizi oluşturduğumuza göre artık Fiori projemizi oluşturalım ve içini yavaş yavaş dolduralım.

ODATA SERVICE İLE SAPUI5 UYGULAMASI OLUŞTURMA

Yine bir önceki yazıda bahsettiğim gibi Orion dosyamızı çalıştıralım. Ve local VPN bağlantımızı yapalım. Local Web Ide linkimizi açalım ve hesabımıza giriş yapalım. Yeni proje oluşturalım.


Bu sefer servis kullanacağımız için Fiori Worklist Application seçeceğiz.


İlerleyelim ve vermek istediğimiz isimlendirmeleri yapalım.


Şimdi oluşturduğumuz destination dosyasını burada seçiyoruz ve kullanıcı bilgilerimizi giriyoruz.


Tamam dedikten sonra eklediğimiz servisimizi seçelim.


Bir sonraki ekranda yapacağımız seçimler:


Burada App Type alanında 2 farklı seçenek var. Eğer SAP Fiori Launchpad Component seçeneğini seçersek, bu uygulamamızı Launcpad uygulaması olarak kullanacağımız anlamına geliyor. Standalone App seçersek de bu uygulamamızı bir link ile direct browserdan çalıştırılabilir bir uygulama olarak kullanabileceğimiz anlamına geliyor. Biz de şimdilik Standalone seçelim.


Aşağıdaki ifadelerin şimdilik çok bir anlamı yok. Zaten uygulamayı geliştirme esnasında tasarım değişecek. Fakat bu ifadelerin ne anlama geldiğini görmek istiyorsanız da bu ekran üzerinde sağda kutucuk içeriisindeki resimde açıklıyor, oradan bakabilirsiniz. İlerleyelim ve uygulamamızı ilk çalıştırdığımızda bizi nasıl bir ekranın karşıladığını görelim:


Burada kullanıcı bilgimizi girelim. Fakat burada girdiğimiz kullanıcı local sistemde değil de farklı sistemlerde yani ERP ve GW sistemlerin ayrı olduğu durumlarda GW kullanıcı bilgilerimizi girmemiz gerekli. Fiori dediğimiz zaman aklımıza GW gelmeli. Uygun kullanıcıyı da girdik ve uygulamamızın henüz veri alışverişi yapmadığımız hali bu şekilde:


Herkesin sorunsuz bir şekilde buraya kadar geldiğine inanıyorum 😊 Şimdi işin esas kısımlarına geldik. Bizim istediğimiz şey veriler ile işlem yapmak. Ve bu verileri tutacağımız bir tablomuz olmalı. Bunun kontrolünü yapabilmek için SE11’de Entity Type’ımızdaki alanları içeren bir tablo oluşturalım.


Şimdi bu tablo üzerinden veri alışverişi ile ekrana data Submitip Submitemediğimizi kontrol edelim. İlk gitmemiz gereken yer yine SEGW’de oluşturduğumuz servisin metotları. Backend kodlamalarımızı bu metotlar içinde yazacağız.


..._DPC_EXT sekmesine girince Inherited Methodslar içinde kullanabileceğimiz methodlar görünüyor. Biz de hangi methodları kullanacaksak sağ tıklayıp Redifine diyoruz ve Redifinitions klasörünün altına alarak içerisine kodlarımızı yazabiliyoruz. Benim daha önce oluşturduğum projede kullanmak üzere Redifine ettiğim metotlar ve açıklamaları:


Kullanacağımız methodları açıkladık. Şimdi öncelik sıramıza göre hangi method kullanacaksak Redifine edelim ve veriler ile oynayalım. Buraya veri alışverişi ile ekrana data Submitip Submitemediğimizi kontrol etmek için gelmiştik. Ekrana veriyi GET methodu ile Submitebiliriz. Bunun için ilk önce GET_Entitityset metodumuzu redefine edelim. Ve içerisinde SE11’de oluşturduğumuz tabloya veri append edelim.


Burada structureımıza verdiğimiz type ET_ENTITYSET, çıktı parametremiz, veri aktardığımız kısımdır. Type’ı bizim oluşturduğumuz entityset tipinde. Yukarıda Signature altında da hangi parametrenin hangi type’a sahip olduğunu görebiliyoruz:


Şimdi buraya yazdığımız datalar Fiori uygulamamızın ekranına gelmiş mi kontrol edelim:


Ekranda gerekli kontrollerin ardından verilerin geldiğini gördük. Şimdi ekranımızı daha da güzelleştirelim ve verileri daha net görelim. Önce nasıl bir tasarım istediğimize karar verelim ve ekrandaki gereksiz alanları view dosyasının içinden silelim. Tasarımı yaptık ve verilerin doğru şekilde ekrana geldiğini tekrar görmüş olduk.


Crud işlemler yapacağımız bir uygulama yapmaya karar vermiştik. Öyleyse benim verileri girebileceğim input alanlar olmalı, bu alanları tanımlayan labeller ile birlikte. Bu alanlara veri girdiğimiz zaman eklediğimiz veya çıkarmak/güncellemek istediğimiz alanların tutulduğu bir tablo olmalı. Butonlar ile bu tabloya veri ekleyip, silip, güncelleme yapabilmeliyiz.

<Table id="table" width="auto" items="{ path: '/ListSet', sorter: { path: 'Ad', descending: false } }" noDataText="{worklistView>/tableNoDataText}" 
       busyIndicatorDelay="{worklistView>/tableBusyDelay}" growing="true" growingScrollToLoad="true" updateFinished="onUpdateFinished">
	<headerToolbar>
		<Toolbar>
			<ToolbarSpacer/>
			<Button type="Accept" icon="sap-icon://add" text="Ekle" press="onKisiEkle"/>
		</Toolbar>
	</headerToolbar>
	<columns>
		<Column>
			<Text text="{i18n>id}"/>
		</Column>
	        <Column>
			<Text text="{i18n>ad}"/>
		</Column>
		<Column>
			<Text text="{i18n>soyad}"/>
		</Column>
		<Column>
			<Text text="{i18n>tarih}"/>
		</Column>
		<Column>
			<Text text=""/>
		</Column>
	</columns>
	<items>
		<ColumnListItem>
			<cells>
				<Text text="{Id}" id="id"/>
				<Text text="{Ad}"/>
				<Text text="{Soyad}"/>
				<Text text="{path: 'Tarih', type: 'sap.ui.model.type.Date', formatOptions: { style: 'medium', UTC : true }}"/>
				<HBox>
					<Button type="Reject" icon="sap-icon://delete" press="onKisiSil"/>
					<Button icon="sap-icon://synchronize" press="onKisiGuncelle"/>
				</HBox>
			</cells>
		</ColumnListItem>
	</items>
</Table>

  • Tarih itemini böyle formatlı şekilde yazmamızın sebebi eğer direct alırsak okunabilir olmayan formatta ekrana gelmesi. Bu nedenle tarih ile ilgili alanları daha okunabilir olması açısından formatlı biçimde yazıyoruz.
  • Daha sonra ekle butonuna basınca açılan dialog ekranımızın tasarımını webapp klasörü altında, fragment klasörü oluşturup bunun içine yazıyoruz.


    Crud.fragment.xml:

    <core:FragmentDefinition xmlns="sap.m" xmlns:l="sap.ui.layout" xmlns:f="sap.ui.layout.form" xmlns:core="sap.ui.core" afterClose="dialogAfterclose">
    	<Dialog title="Aktivite Detayları" id="dialogEmployeeInstead">
    		<content>
    			<f:SimpleForm editable="true" layout="ResponsiveGridLayout" labelSpanXL="3" labelSpanL="3" labelSpanS="12" adjustLabelSpan="false"
    				emptySpanXL="4" emptySpanL="4" emptySpanM="4" emptySpanS="0" columnsXL="1" columnsL="1" columnsM="1" singleConteinerFullSize="false">
    				<f:content>
    					<Label text="{i18n>id}"/>
    					<Input id="inputKisiId"/>
    					<Label text="{i18n>ad}"/>
    					<Input id="inputKisiAd"/>
    					<Label text="{i18n>soyad}"/>
    					<Input id="inputKisiSoyad"></Input>
    					<Label text="{i18n>tarih}"/>
    					<DatePicker id="dpKayitTarih"/>
    				</f:content>
    			</f:SimpleForm>
    		</content>
    		<buttons>
    			<Button text="{i18n>kaydet}" press="onKayitFrag" type="Transparent"/>
    			<Button text="{i18n>geri}" press="onGeriFrag" type="Transparent"/>
    		</buttons>
    	</Dialog>
    </core:FragmentDefinition>

    Ve controller içinde butona basınca fragment dosyasını açması için gerekli komutları Worklist.controller.js içine yazalım:

    onKisiEkle: function() {
    			this._getKayitDialog().open();
    		},
    
    		onGeriFrag: function() {
    			this._getKayitDialog().close();
    		},
    
    		_getKayitDialog: function() {
    			if (!this._oKayitDialog) {
    				this._oKayitDialog = sap.ui.xmlfragment("abapegitim.zsg_crud_deneme.fragment.crud", this);
    				this.getView().addDependent(this._oKayitDialog);
    				jQuery.sap.syncStyleClass("sapUiSizeCompact", this.getView(), this._oKayitDialog);
    			}
    			return this._oKayitDialog;
    		}

    Uygulamadan çıkmadan dialog ekranını her açışımızda eski dataları görmemek ve dialog pop-up’ını yenilemek için:

    dialogAfterclose: function(oEvent) {
            sap.ui.getCore().byId("inputKisiId").setValue("");
            sap.ui.getCore().byId("inputKisiAd").setValue("");
    	sap.ui.getCore().byId("inputKisiSoyad").setValue("");
    	sap.ui.getCore().byId("dpKayitTarih").setDateValue(new Date());
    }

    Verileri ekleme işlemini ayrı bir pop-up ile yapmak daha estetik durdu.


    Şimdi gerçek verilerle dinamik bir yapı kurmaya başlayabiliriz. İlk olarak verileri kaydedeceğimiz SE11’de oluşturduğumuz tablodaki verileri uygulama ekranımızdaki tabloda gösterilmesini sağlamak için GET_ENTITYSET methodumuzun içini bu şekilde dolduralım:

    LISTSET_GET_ENTITYSET

      METHOD listset_get_entityset.
        DATA lt_list TYPE TABLE OF zsgt_crud_deneme.
        SELECT *
          FROM zsgt_crud_deneme
          INTO TABLE lt_list.
        et_entityset = VALUE #( FOR ls_list IN lt_list
                              ( id    = ls_list-id
                                ad    = ls_list-ad
                                soyad = ls_list-soyad
                                tarih = ls_list-tarih ) ).
      ENDMETHOD.

    Şimdi bu tabloya veri eklemek için CREATE_ENTITY methodumuzun içini dolduralım:

    LISTSET_CREATE_ENTITY

        DATA: ls_data TYPE zcl_zsg_crud_deneme_mpc=>ts_list,
              ls_list TYPE zsgt_crud_deneme.
        io_data_provider->read_entry_data( IMPORTING es_data = ls_data ).
        MOVE-CORRESPONDING ls_data TO ls_list.
        MODIFY zsgt_crud_deneme FROM ls_list.
        IF sy-subrc = 0.
          er_entity = ls_data.
        ENDIF.

    Backend kodlamasını yaptığımız işlemlerin Fiori tarafında kontrol edilmesi gerekli. Burada dialog ekranımızda verileri girdikten sonra tabloya eklenmesi için gerekli ifadeleri yazıyoruz:

    Worklist.controller.js

    onKayitFrag: function() {
    	var that = this;
    	var vId = sap.ui.getCore().byId("inputKisiId").getValue();
    	var vAd = sap.ui.getCore().byId("inputKisiAd").getValue();
    	var vSoyad = sap.ui.getCore().byId("inputKisiSoyad").getValue();
    	var vTarih = this.resolveTimeDifference(sap.ui.getCore().byId("dpKayitTarih").getDateValue());
    	var sData = {
    		Id: vId,
    		Ad: vAd,
    		Soyad: vSoyad,
    		Tarih: vTarih
    	};
    	sap.ui.core.BusyIndicator.show(); //Bir işlem devam ederken başka işlem başlatmamak için
    	this.getView().getModel().create("/ListSet", sData, {
    		success: function(oData) {
    			sap.ui.core.BusyIndicator.hide();
    			that._getKayitDialog().close();
    			that.onRefresh();
    			MessageToast.show("Başarılı");
    		},
    		error: function(oError) {
    			sap.ui.core.BusyIndicator.hide();
    			that._getKayitDialog().close();
    			MessageToast.show("Başarısız");
    		}
    	});
    }

    Bu fonksiyon gün değişkeni yalın halde kullanınca arka tarafta bir gün eksik olarak tutulduğudan, doğru zamanı bulmak için kullanılır:

    resolveTimeDifference: function(dateTime) {
    	if (dateTime !== undefined && dateTime !== null && dateTime !== "") {
    		var offSet = dateTime.getTimezoneOffset();
    		var offSetVal = dateTime.getTimezoneOffset() / 60;
    		var h = Math.floor(Math.abs(offSetVal));
    		var m = Math.floor((Math.abs(offSetVal) * 60) % 60);
    		dateTime = new Date(dateTime.setHours(h, m, 0, 0));
    		return dateTime;
    	}
    	return null;
    }

    Gördüğünüz gibi tabloya verilerimizi kaydettik. Kontrol ettiğinizde se11’de oluşturduğunuz tabloya da bu verilerin kaydolduğunu göreceksiniz. Aynı şekilde silme ve güncelleme işlemleri için gerekli kontrolleri yapalım:

    LISTSET_DELETE_ENTITY

      METHOD listset_delete_entity.
        DATA : ls_key_tab TYPE /iwbep/s_mgw_name_value_pair,
               lv_list TYPE zsgt_crud_deneme-id.
        READ TABLE it_key_tab INTO ls_key_tab WITH KEY name = 'Id'.
        lv_list = ls_key_tab-value.
        IF lv_list IS NOT INITIAL.
          DELETE FROM zsgt_crud_deneme WHERE id = lv_list.
        ENDIF.
      ENDMETHOD.

    LISTSET_UPDATE_ENTITY

      METHOD listset_update_entity.
        DATA: ls_request_input_data TYPE zcl_zsg_crud_deneme_mpc=>ts_list,
              ls_key_tab            TYPE /iwbep/s_mgw_name_value_pair,
              lv_list               TYPE zsgt_crud_deneme-id,
              ls_test               TYPE zsgt_crud_deneme.
    
        READ TABLE it_key_tab WITH KEY name = 'Id' INTO ls_key_tab.
        lv_list = ls_key_tab-value.
        IF lv_list IS NOT INITIAL.
          io_data_provider->read_entry_data( IMPORTING es_data = ls_request_input_data ).
          UPDATE zsgt_crud_deneme SET id    = ls_request_input_data-id
                                      ad    = ls_request_input_data-ad
                                      soyad = ls_request_input_data-soyad
                                      tarih = ls_request_input_data-tarih
                                  WHERE id = lv_list.
          IF sy-subrc IS INITIAL.
            er_entity = ls_request_input_data.
          ENDIF.
        ENDIF.
      ENDMETHOD.

    Worklist.controller.js:

    onInit: function() {
    	var sData = {
    		Id: "",
    		Ad: "",
    		Soyad: "",
    		Tarih: new Date() //new Date() bugünün tarihini veriyor. Bu alanı boş bırakınca hata verdiği için bugünün tarihini verdik.
    	};
    	var oModel = new JSONModel(sData);
    	this.getView().setModel(oModel, "list");
    },
    
    onKisiSil: function(oEvent) {
    	var that = this;
    	var vId = oEvent.getSource().getParent().getBindingContext().getProperty("Id");
    	var vAd = oEvent.getSource().getParent().getBindingContext().getProperty("Ad");
    	var vSoyad = oEvent.getSource().getParent().getBindingContext().getProperty("Soyad");
    	var vTarih = oEvent.getSource().getParent().getBindingContext().getProperty("Tarih");
    	var sData = this.getView().getModel("list").getData();
    	this.getView().getModel().remove("/ListSet('" + vId + "')", {
    		method: "DELETE",
    		success: function(data) {
    			MessageToast.show(vId + " numaralı aktivite silindi.");
    		},
    		error: function(e) {
    			MessageToast.show(vId + " numaralı aktivite silinemedi.");
    		}
    	});
    },
    
    onKisiGuncelle: function(oEvent) {
    	var sLineData = oEvent.getSource().getParent().getBindingContext().getProperty();
    	this._getKayitDialog().open();
    	sap.ui.getCore().byId("inputKisiId").setValue(sLineData.Id);
    	sap.ui.getCore().byId("inputKisiAd").setValue(sLineData.Ad);
    	sap.ui.getCore().byId("inputKisiSoyad").setValue(sLineData.Soyad);
    	sap.ui.getCore().byId("dpKayitTarih").setDateValue(sLineData.Tarih);
    	this.vMethod = "UPDATE";
    }

    Bütün bu adımların ardından CRUD işlemler yapabildiğimiz temel bir uygulamayı tamamlamış olduk. Uygulamanın eksiklikleri ya da daha nasıl kullanışlı ve güzel hale getirilebilir kısmı bizlere kalmış😊. Görselleri ile detaylı bir şekilde anlatmaya çalıştığım bu yazı umarım ilgilenenler için faydalı bir yazı olmuştur.

    Okuduğunuz için teşekkür ederim, bir sonraki yazıda görüşmek üzere.

    ← Bloglara geri dön