סוד ההצלחה של ABAP ושל SAP ERP

מה זה ABAP?

ABAP הינה שפה פנימית של חברת SAP המשמשת לפיתוח אפליקציות עסקיות בתוך מערכת הERP. השפה משמשת אך ורק בתוך מערכת הSAP ואי אפשר לכתוב תוכניות ABAP מחוץ למערכות SAP. השפה פותחה בשנות ה80 ונחשבת מיושנת מבחינת סינטקס (מזכירה קצת את קובול).

כבר שנים מתכנתים רבים נוטים לזלזל בשפה ולחשוב עליה כמיושנת. למי שרגיל לכתוב בשפות בסגנון C כמו: C,C++,Java,C# קשה להתרגל לפקודות ולסינטקס השונה. בנוסף, העובדה שרק לאחרונה נוספה תמיכה בOOP ושרק מעטים משתמשים באופציה זו הינה עוד פקטור שהופך את השפה לפרימיטיבית.

מצד שני, הלקוחות עובדים שנים רבות עם השפה ומרוצים ממנה באופן כללי. למעשה היא נותנת מענה מצוין לכתיבת אפליקציות עסקיות והתוצאות מהירות וטובות יותר מאשר בכל שפה אחרת כולל פלטפורמות מודרניות כמו Java או .NET מה הופך את ABAP לכזה סיפור הצלחה? כיצד קרה שמרבית הטרנזקציות העסקיות בעולם עוברות דרך שפה זו ובעולם ממשיכים לפתח בה ללא הפסקה? להלן 7 סיבות להצלחת השפה

אינטגרציה מלאה

כשמתכנת כותב בשפה כלשהי הוא משתמש בכמה מוצרי תוכנה: שרת אפליקציות, סביבת פיתוח, סביבת הרצה, בסיס נתונים, מערכת ניהול גרסאות ולעיתים עוד כמה תוכנות. האינטגרציה בין התוכנות השונות גם כשהן מגיעות מאותו ספק (מיקרוסופט למשל) גוזלת זמן ומשאבים. ABAP רצה בתוך מערכת הSAP כך שאין צורך אפילו בסביבת פיתוח (הקליינט של SAP הוא הסביבת פיתוח). נכנסים למערכת, טוענים את התוכנית ומתחילים לקודד.

אין הפרדה בין השכבות

כיוון שהDB הוא חלק ממערכת הSAP, וכך גם הקליינט, אין למעשה הפרדה בין שכבת הUI לשכבת הדאטה וללוגיקה. בABAP אין מודלים של MVC ואין ORM או שכבות DAL. אתה רוצה לכתוב לטבלה? השפה עצמה "מכירה" את כל הטבלאות והמבנים של המערכת ואין צורך להתחבר דרך ממשק כלשהו כפי שנעשה בדוט נט (ADO) או בג'אווה (JDCB). הGUI הוא אחיד ורץ בקליינט של SAP. לא צריך לדאוג לתמיכה בדפדפנים או להתנהגות של אלמנטים שונים בHTML.

אין Client-Server

טוב, זה לא שבאמת את קליינט סרבר אבל מבחינת המתכנת אין משמעות למה שרץ בצד השרת ומה רץ בצד הקליינט. חיברת ListBox לרשימה עם 40,000 פריטים? אין בעיה. SAP תדאג להציג את זה בצורה האופטימלית עם או בלי דפדוף והכל ינגן כמו שצריך.

אין שפות סקריפט שרצות בדפדפן, אין ניהול של sessions וcookies ולא צריך לדאוג לstate של התוכנית.

אין ריבוי משתמשים

כמובן שיש ריבוי משתמשים אבל מבחינת המתכנת הוא רץ ב"עולם" משלו והוא מסתכל רק על המשתמש שכרגע logged in.

מוגבלות זה כוח

בABAP אין ספריות native. אי אפשר לפתח API בABAP שיתממשק לפייסבוק וישלח הודעות לטוויטר. השפה נועדה רק לתוכניות עסקיות וככזו היא מוגבלת לסט הפקודות שSAP מספקת. החיסרון הוא שאי אפשר למשל לפתח רכיבי UI חדשים ותוכניות SAP תמיד ייראו באותו סגנון. מצד שני, מי בכלל צריך את זה? למה שמנהל לוגיסטי ירצה פתאום לשלב את חדשות מזג האוויר של Yahoo בתוך אפליקציית ניהול המחסנים שלו? המסכים הסטנדרטים מספקים כל מה שצריך בשביל העבודה והעובדה שאין אפשרות להכניס טכנולוגיות מבחוץ (למעט קוד ABAP אחר) מבטיחים יציבות ופשטות.

תכנות פרוצדוראלי

מסיבה כלשהי, כנראה שלמוח האנושי יותר קל לחשוב בצורה של פרוצדורות/פונקציות מאשר בצורה של OO ואובייקטים. אני נתקל במיישמים רבים שמדברים על טבלאות ושדות והפעלה של פונקציות אבל כשמדברים איתם על הורשה לא מבינים מה רוצים מהם. אמנם כיום יש OO ABAP אבל רוב תוכניות הABAP כתובות בצורה המסורתית של פונקציות וזה בסדר גמור. היישויות בABAP הם לרוב מבנים שקיימים בתוך מערכת הSAP ודומות לstruct שמכיל נתונים אך לא פונקציות. הפונקציות בABAP יכולות לקבל טבלאות כפרמטרים ולהחזיר טבלאות בהתאם אך הפונקציות לא שייכות לישויות עצמן כמו בOO.

סטנדרטים קשוחים לניהול קוד

מערכות SAP דורשות סטנדרטים קבועים בכל מה שקשור לשמות של פונקציות ועצמים, מתחמי שמות (name spaces), חבילות, ספריות וכו'. מהבחינה הזו, למתכנת ABAP אין חופש פעולה מבחינת הארכיטקטורה. הכל מוגדר מראש ורק צריך לממש את הפונקציונליות עצמה. המטרה היא להתמקד בקוד העסקי ולא במבנה הקוד והחלוקה שלו.

בנוסף, כל ניהול הגרסאות, העברה בין סביבות (DEV, QA, PROD) נעשות במתודולוגיה קבועה שמוטמעת בתוך המערכת. אי אפשר להעביר "פרוייקט" על דיסק און קי ולהעלות לשרת כמו בשפות אחרות.

לסיכום, על מנת לכתוב תוכנית ABAP מספיק להכיר את הפקודות של השפה, את הנהלים של כתיבת ABAP וכמה אלמנטים של UI. מי שתוהה איך המתכנת אמור להכיר את מאות אלפי האובייקטים (המכונים Business Object), היישויות במערכת והטבלאות אז גם לזה יש פתרון: מיישם ERP. תפקידו של המיישם להכיר את המודול הרלוונטי מכל הכיוונים והוא זה שמגדיר ומאפיין למתכנת לאילו אובייקטים לכתוב ואת מהלך התוכנית. למתכנת נשאר רק לממש את הקוד מה שמסביר למה מהנדסי תוכנה לא אוהבים ABAP…

כך נראית סביבת הפיתוח

SAP ABAP Workbench
SAP ABAP Workbench

העברת טופס לXML מAdobe Flex ActionScript3 לJava ובחזרה

כמתכנת FLEX פרילנסר עצמאי, מצאתי מקרים בהם הלקוח לא עובד בFramework מסודר לתקשורת Client-Server. במקרים כאלה לעיתים יש צורך לעשות Serialize לטופס ולהפוך אותו לפורמט גנרי כמו XML על מנת לשלוח אותו לשרת כלשהו (למשל J2EE Server) ע"י פרוטוקול כמו HTTP או SOAP Web Service. הנה פונקציה פשוטה שכתבתי שלוקחת טופס FLEX, מבצעת עליו סריקה רקורסיבית ושולפת את כל השדות שלו (TextInput) ומחזירה XML עם רשימת השדות והערכים שלהם.
כמו כן מצורף קוד בג'אווה שלוקח XML כזה והופך אותו לJava Bean ובמידה ובBean יש properties תואמים הם יקבלו את ערכי השדות. יש גם פונקציה הפוכה שלוקחת XML וממלאת את השדות בטופס FLEX.

יש להוסיף את הספריות האלה לקוד:

	import flash.utils.Dictionary;
	import mx.controls.TextInput;
	import mx.core.Container;

הנה הפונקציה (למעשה שתי פונקציות) שלוקחת טופס ומחזירה XML:

		public static function viewToXml(className:String, view:Container):String{
			var xml:XML = <bean class={className}></bean>;
			var xmlFinal:XML = addControlsToXml(xml, view);
			return '<?xml version="1.0" encoding="UTF-8"?>' + xmlFinal;
		}
		
		private static function addControlsToXml(xml:XML, view:Container):XML{
			var controls:Array = view.getChildren();
			for (var i:int; i<controls.length; i++){
				if (controls[i] is TextInput){
					var component:TextInput = controls[i] as TextInput;
					var child:XML = <property name={component.id}>{component.text}</property>;
					xml.appendChild(child);
				}
				if (controls[i] is Container){
					addControlsToXml(xml, controls[i] as Container);
				}
			}
			return xml;
		}

והפונקציה שמקבלת XML וממלאת את הטופס בהתאם:

		private static function xmlToView(props:Dictionary, view:Container):void{
			var controls:Array = view.getChildren();
			for (var i:int; i<controls.length; i++){
				if (controls[i] is TextInput){
					var component:TextInput = controls[i] as TextInput;
					if (props[component.id] != null){
						component.text = props[component.id];
					}
				}
				if (controls[i] is Container){
					xmlToView(props, controls[i] as Container);
				}
			}
		}

ועכשיו לקוד בג'אווה. תצטרכו להוסיף את הספריות הבאות:

import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

קבלת XML והפיכתו לJava Bean:

	public static Object xmlToBean(String xmlStr){
		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder documentBuilder = null;
		try {
			documentBuilder = documentBuilderFactory.newDocumentBuilder();
			Document doc = documentBuilder.parse(new ByteArrayInputStream(xmlStr.getBytes("UTF-8")));
			XPath xpath = XPathFactory.newInstance().newXPath();
			Node bean = (Node)xpath.evaluate("//bean", doc, XPathConstants.NODE);
			if (bean != null){
				NamedNodeMap attributes = bean.getAttributes();
				Node cls = attributes.getNamedItem("class");
				String clsName = cls.getNodeValue();
				Object obj = Class.forName(clsName).newInstance();
				NodeList props = (NodeList)xpath.evaluate("//property", doc, XPathConstants.NODESET);
				Class cl = obj.getClass();
				for (int i=0; i<props.getLength(); i++){
					String propName = props.item(i).getAttributes().getNamedItem("name").getNodeValue();
					String propValue = props.item(i).getTextContent();
					String ch = propName.substring(0, 1).toUpperCase();
					String methodName = "set" + ch + propName.substring(1);
					Method[] methods = cl.getMethods();
					for (Method method : methods){
						if (method.getName().equals(methodName)){
							Class[] paramTypes = method.getParameterTypes();
							for (Class param : paramTypes){
								if (param.getName().equals("java.lang.String")){
									method.invoke(obj, propValue);
								}
								else if(param.getName().equals("int")){
									method.invoke(obj, Integer.valueOf(propValue));
								}
								else if(param.getName().equals("long")){
									method.invoke(obj, Long.valueOf(propValue));
								}
							}
						}
					}
				}
				return obj;
			}
		} catch (Exception e) {
			log.error(e);
		}
		return null;
	}

והחלק האחרון: לקחת Java Bean ולהפוך אותו לXML:

	public static String beanToXml(Object obj){
			DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder documentBuilder = null;
			try {
				documentBuilder = documentBuilderFactory.newDocumentBuilder();
			} catch (ParserConfigurationException e) {
				log.error(e);
			}
			Document doc = documentBuilder.newDocument();
			Element rootElement = doc.createElement("bean");
			rootElement.setAttribute("class", obj.getClass().getName());
			Method[] getters = obj.getClass().getMethods();
			for (Method getter : getters){
					if (getter.getName().indexOf("get") == 0)
					if (getter.getParameterTypes().length == 0)
					if (!getter.getName().equals("getClass")){
						StringBuffer prop = new StringBuffer(getter.getName().substring(3));
						String ch = prop.substring(0, 1).toLowerCase();
						prop.replace(0, 1, ch);
						
						Element propElement = doc.createElement("property");
						propElement.setAttribute("name", prop.toString());
						try {
							propElement.setTextContent(getter.invoke(obj).toString());
						} catch (Exception e) {
							e.printStackTrace();
						}
						rootElement.appendChild(propElement);
					}
			}
			doc.appendChild(rootElement);
			
			StringWriter stw = new StringWriter(); 
            Transformer serializer;
			try {
				serializer = TransformerFactory.newInstance().newTransformer();
				serializer.transform(new DOMSource(doc), new StreamResult(stw));
			} catch (Exception e) {
				log.error(e);
			}
            return stw.toString();
	}