עבר זמן מאז כתבתי פה ודברים קצת התעדכנו. אז בשנתיים האחרונות החבילה המומלצת שלנו מתבססת על Node.js. מהיר, פשוט, קל להתקנה לתחזוקה ולדיבוג, הרבה פחות מסורבל לעומת ג'אווה ומאפשר תאימות וחלקי קוד משותפים בין השרת לקליינט. כמובן שJava עדיין מככבת בפרוייקטים שלנו, במיוחד בפרוייקטים הוותיקים וגם סתם אצל לקוחות שבוחרים לפתח בשפה זו אבל כשאנחנו נדרשים להקים מערכת מאפס ולבחור ארכיטקטורה node הוא בהחלט הבחירה שלנו. בפוסטים הבאים נעלה טיפים, רעיונות, פתרונות וPatterns לבעיות נפוצות.
האדם (העובד) הורסטילי
לאחרונה יוצא לי יותר ויותר להעזר בשירותיהם של אנשי מקצוע שונים: קריאיטיב, מרקום, מפיקי סרטונים, מעצבי מוצר, מעצבים גרפיים, בוני אתרים, אנשי שיווק ועוד. אם יש משהו שמשגע אותי זה שבשנת 2015 אנשים מרשים לעצמם לא להיות ורסטילים ועוד בתחום ההי-טק. יתרה מכך, אני מוצא יתרון אדיר לביצועיסטים שיכולים לקחת כל משימה על פני ה"מומחים" שלא זזים מפינת הנוחות שלהם.
דוגמאות? בבקשה:
צלמי סטילס שלא עושים ווידאו, צלמי וידיאו שלא עושים עריכה, אנשי מרקום שלא עושים שיווק ברשתות חברתיות ("אני רק כותבת את התכנים"), אנשי שיווק שרק "מתמחים בקידום אפליקציות", אנשי שיווק לא מוכנים לעשות את השיווק אלא "אנחנו מתכננים את האסטרטגיה". מה לעזאזל אני אמור לעשות עם "אסטרטגיה"??? אחרים כותבים אבל "מצטערת, לא יודעת לכתוב על דברים טכניים", מעצבי אתרים שלא עושים את התכנות או לא עובדים עם מתכנת-שותף (עושים רק את העיצוב), מתכנתי קליינט שלא עושים סרבר ולהפך, מתכנתי אנדרואיד שלא כותבים iOS, מומחה OpenCV שמוכן לכתוב רק לWindows והרשימה ארוכה עד מאוד.
זה לא שיש פסול בלהיות מומחה בתחום ספציפי אבל בעולם התחרותי של היום, אנשים מתלוננים שאין מספיק עבודות אבל לא מוכנים להכנס לגוגל ולהשקיע קצת בללמוד משהו שהוא מעבר ל2 ס"מ שהם מתעסקים בו. באמת צלם וידיאו לא יכול ללמוד לערוך סרטון? ויוצרי סרטוני אנימציות לא יכולים לערוך סרטון רגיל על הדרך? מדובר בהבדלים כאלה גדולים? מדע טילים?
תבינו חברים יקרים, אני מבין שכולכם למדתם תארים וקורסים אבל היום עם גוגל והאינטרנט וכל המידע הפרוס, אין שום תירוץ לא להיות רב תחומיים. לא אומר שמתכנת צריך לדעת לייצג לקוחות בבג"צ או שרופא משפחה יעשה ניתוחי מוח, אבל קצת, קצת ראש גדול.
ולמי שדואג שיעשה עבודה לא מקצועית: המבחן צריך להיות האם הנזק הפיך. גם אם תכנתת אפליקציה גרועה, קוד לא מושלם, עריכה בינונית, כתבה לא מדוייקת תמיד אפשר לשנות. רק אם הפסדת תיק בבית משפט, הרגת בנאדם על שולחן הניתוחים או ירית לכיוון הלא נכון אז אי אפשר לתקן.
אז אנשים יקרים, קחו נשימה, תקנו ספר, כנסו לאינטרנט, חפשו בפורומים, תלמדו מה שצריך ותנו פתרון כולל. אין יותר "אני מומחה לכתיבת קלאסים בC++ גרסה 12.5 במערכת הפעלה לינוקס הפצת רדהאט".
אגב, ביצועיסטים אמיתיים, כאלה שיקחו כל משימה (וגם ילמדו אותה לעומק ויעשו אותה ברצינות) הם בני אלמוות. יש להם עבודה מגיל 17 ועד גיל 65 (ואני מכיר עובדים בשני הגילאים האלה), הם תמיד מבוקשים, תמיד מרוויחים מעולה, תמיד צעד אחד לפני כולם והעיקר: תענוג לעבוד איתם.
פרידה מWindows ביי ביי Microsoft
אחרי שנים ארוכות שבהן חטאתי ונשארתי נאמן לwindows הישן והטוב, מצאתי לנכון סוף סוף לפרמט את המחשב ולהתקין אובונטו על הלפטופ האישי שלי (ליתד דיוק Kubuntu כי אני לא אוהב את ממשק הUnity). אז מה גרם לי לעבור? אולי כדאי קודם לשאול מה גרם לי להתקין מערכת הפעלה בתשלום?
התשובה פשוטה:
הwindows מגיע עם המחשב וקשה היום לקנות לפטופ נורמלי בלי ערכת הפעלה מותקנת מראש. זה נראה לי מטופש לזרוק 100 דולר לפח אז נשארתי עם מה שיש.
לקוחות שולחים אקסלים ופאוורפוינטים שלא תמיד נפתחים כמו שצריך בליברה אופיס או בגוגל דוקס.
אז למה בכל זאת עברתי?
קודם כל, כי windows 8 כל כך מזעזעת, לא ידידותית ולא אינטואיטיבית שלא יכולתי לסבול את המחשבה לעבור אליה
דבר שני, מיקרוסופט פתחה את צוואר הבקבוק ומאז שיש אופיס בענן (Office 365) אז כבר לא צריך התקנה מקומית של אופיס. מקסימום משלמים כמה דולרים לחודש.
לסיום: כמתכנת, העלות עלתה על התועלה. רוצה להתקין רובי? בהצלחה עם windows. פיתון? חצי מהספריות לא מתאימות במיוחד כל מה שקשור לסיסטם. להריץ דברים שכתבתי לרסברי פיי על חלונות? נו באמת .Hadoop? צריך מליון מעקפים שאפילו לא ניסיתי. לקמפל דברים עם GCC או להריץ דברים בCygwin ממש לא בא לי על windows.
גם להריץ VM ולהקריב את ביצועי המכונה אני לא רוצה מה גם שעם דיסק SSD אני מוגבל במקום הפנוי וכל MB הוא יקר.
בקיצור, מאז שעברתי החיים נפלאים. אני בכלל לא מתגעגע, לא חסר לי כלום, את הדיסק הקודם ארזתי במארז USB לכל צרה שלא תבוא אבל כמה שבועות שאני לא צריך כלום. וכל הדברים שרציתי להריץ? עובדים נפלא על המכונה שלי.
למה אני עובר מג'אווה לRuby On Rails?
לאחרונה החלטנו להתחיל לפתח ברובי וליתר דיוק ברובי און ריילס. אלפי מאמרים נכתבו בנושא זה, מפרטים ומדגימים את היתרונות בטכנולוגיה זו ואת הקלות והמהירות בה ניתן לפתח מערכות Web. ובכל זאת, לאחר כמעט עשור של תכנות בJava ובעיקר בJ2EE וחלופותיו הפתוחות (Spring, Hibernate וכו') אני מוצא לנכון לפרוס את הסיבות למעבר לרובי.
כי נמאס מהסירבול של Java
אני עובד בג'אווה, בונה אפליקציה יפה עם כמה מסכים. כמות הJars שאני צריך להביא, תהליך בניית האפליקציה (הWAR) והDeploy הארוך גוזלים זמן יקר בכל פעם שרוצים לשנות אפילו שדה אחד מסכן. זה מתחיל בבנייה ארוכה בMaven שתוצאתו קובץ ענק של עשרות MB, ואז צריך להעלות את הקובץ לשרת מה שיכול לקחת דקות ארוכות כשמדובר בCloud, מה שגורם לאתחול האפליקציה (עוד כמה דקות) ולבסוף לבדוק ולגלות ששוב צריך לשנות משהו. התהליך הזה בלתי נסבל. אמנם יש דרכים לעקוף את זה כמו העתקת קובץ JSP ישירות לשרת אבל זה עובד רק בתנאים מסויימים ובוודאי שזו לא דרך מומלצת.
כי האובייקטים בג'אווה הם Strong typed
אני רוצה דבר פשוט: לשלוח אובייקט מJavaScript לשרת, לבצע פעולה כלשהי בשרת ולשלוח בחזרה תשובה לJS. עם JSON זה אמור להיות קל. הstring הופך לאובייקט JavaScript ולהפך. אבל בג'אווה אי אפשר ליצור אובייקט בלי להגדיר לו מחלקה מתאימה מראש. זה אומר או לפרסר את הJSON עם parser כלשהו, או להגדיר קלאס עם שדות ואנוטציות מתאימות ולהשתמש בפרימוורק כמו Jackson. זה אומר שכל שינוי בשדות של הJSON יחייב שינוי בקוד של הג'אווה. הסירבול והסיבוך במעבר מידע בין קליינט לסרבר הוא בעייתי ולא כדאי.
כי ריבוי הפרימוורקים והשכבות מסבך את העבודה
נסו לחבר את Spring וHibernate. לא מסובך מאוד למי שיודע מה הוא עושה. נסו לחבר את זה לGWT, זה כבר כן מסובך כי אין דרך סטנדרטית. עכשיו בואו נגדיר שדה בHibernate, נשתמש בו בDAO (מוזרק דרך ספרינג, אלא מה?), שמתמפה לשדה בטופס GWT. זה כבר מצריך לא מעט עבודה. הטכנולוגיות כל כך רבות, השכבות השונות והחיבורים בינהם מויאים את החשק לפתח. כולם כותבים MVC אבל מה עם Model שמתחבר באופן טבעי לView שמדבר בצורה נורמלית עם הController?
בשביל פיתוח סטנדרטי, יותר מדי טכנולוגיות רק מזיקות
ג'אווה נהדרת. יש אינספור טכנולוגיות, פרימוורקים וספריות. אני יכול לאנדקס מסמכים בLucene, להריץ חישובים מבוזרים בHadoop, לשמור מידע בMySQL או בnoSQL או בGoogle App Engine או במליון דרכים אחרות. אבל בואו נודה בזה, חלק גדול מהאפליקציות דורשות פחות או יותר אותו דבר: קצת לוגיקה בשרת, UI משובח מבוסס JS וAJAX, ושמירת מידע בRDBMS. לא מדובר על מדע טילים ולא צריך אלף פרימוורוקים מתוחכמים בשביל זה. לכן אני מוותר על הגמישות והתשתיות של ג'אווה לטובת סטנדרט אחיד ופשוט, אינגרטיבי באופן מלא, קריא, קליל, מהיר ונוח.
אז מה עם ג'אווה בכל זאת?
Java בהחלט לא הולכת לפח. היא תשאר שפה חשובה וחזקה והטכנולוגיות הרבות שפותחו סביבה ימשיכו לשרת אחרים ואותי עוד שנים ארוכות. בהחלט אשקול פיתוח פרוייקטים בג'אווה במקרה הצורך אבל כשמדובר בפיתוח WEB פשוט, אני אבחר במשהו קליל ומהיר יותר וכרגע Ruby On Rails עונה בדיוק על הדרישות.
איך עובד oAuth 2.0? דוגמא מFacebook API
אתרים שונים מספקים API חיצוני על מנת לאפשר למפתחים לכתוב אפליקציות ותוכנות המתממשקות לאתר. הידועים שבהם: פייסבוק, גוגל, טוויטר וכו' מאפשרות למפתחים חיצוניים לגשת לפרופיל של המשתמש הרשום, לבצע פעולות בשמו, לקבל מידע, לבצע עדכונים וכו'. על מנת לנהל את ההתממשקות וההרשאות של משתמשי הAPI נעשה שימוש במנגנון אישור בשם oAuth. במאמר זה נסקור את הטכניקה הזו ואת היישום שלה בפועל בAPI של Facebook. נושא זה הכרחי כמעט לכל Facebook Application Developer ולמעשה לכל מי שרוצה לפתח אפליקצייה לפייסבוק או אתרים דומים (גוגל, טוויטר ועוד).
כאשר אפליקציה כלשהי פונה לפייסבוק ומבקשת לבצע פעולה על פרופיל של משתמש, פייסבוק צריכים להבטיח שהמשתמש אישר את הפעולה. הדרך הפשוטה ביותר זה שהAPI יעביר את המשתמש והסיסמה של המשתמש וכך פייסבוק יידעו שהאתר מורשה. כמובן שזה מחייב שהאתר החיצוני (משתמש הAPI) יבקש את המשתמש והסיסמה של משתמש הפייסבוק. זה גורר בעיית אבטחה חמורה שכן הסיסמה נשמרת באתר חיצוני, האתר יכול עם הסיסמה הזו לבצע כל פעולה שירצה, הסיסמה עלולה להיות לא מוגנת וכו'.
הדרך לפתרון שמציע oAuth עובד בצורה הבאה:
מפתחי האתר כותבים אפליקציית פייסבוק ריקה (ללא תוכן). באפליקציה זו מגדירים את הדברים הבאים:
- אילו הרשאות האפליקציה מבקשת מהמשתמש? (צפייה, עדכון, וכו')
- הדומיין של האתר mydomain.com
כשהמשתמש רוצה לאפשר לאתר שימוש בAPI, האתר מפעיל לינק בדפדפן במבנה הבא:
https://www.facebook.com/dialog/oauth?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL&scope=email,read_stream
הפעלת הלינק הזה על ידי הדפדפן (redirect פשוט) תעלה למשתמש את המסך כניסה לפייסבוק:
מסך זה חשוב מאוד, הוא מאפשר את הlogin לFacebook כך שהאימות נעשה בין המשתמש לפייסבוק אבל בשום שלב המשתמש לא מוסר את הפרטים שלו לאתר החיצוני.
לאחר האימות, פייסבוק יבקש מהמשתמש לאשר את התקנת האפליקציה בפרופיל. מסך זה יציג למשתמש את ההרשאות שהאפליקציה מבקשת כפי שהוגדרו בלינק, והמשתמש יוכל להחליט אם לאשר או לדחות.
כעת פייסבוק יודע שהמשתמש אישר לאתר לבצע פעולות מסויימות.
במידה והמשתמש אישר את האפליקציה, פייסבוק יעשו redirect לכתובת שצויינה בבקשה המקורית (הועבר כפרמטר בURL). הURL שפייסבוק שולח יכלול פרמטר בשם code. עם הפרמטר הזה נפנה לפייסבוק על מנת לקבל את הaccess_token, שהוא הקוד הסופי שמאפשר קריאות API לפייסבוק.
https://graph.facebook.com/oauth/access_token?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL&client_secret=YOUR_APP_SECRET&code=THE_CODE_FROM_ABOVE
פייסבוק יאשר את הקוד וישלח לכתובת שמצויינת בredirect_uri את התשובה: access_token שייראה בערך כך:
הקוד הזה ישמש אותנו לקריאות API. כל קריאת API (מבוססת REST) מאפשרת שירשור של הaccess_token כפרמטר. כך אנו יכולים לבצע קריאות לפייסבוק ופעולות על משתמש מסויים, לאחר שאישר אותנו מבלי לבקש פרטי משתמש. פייסבוק יאפשרו פעולות רק מתוך הדומיין שהוגדר באפליקציה כדי למנוע מצבים בסגנון cross site scripting. הaccess_token תקף למשך הזמן שהוגדר לו. אפשר גם ליצור token שאינו פג לעולם, תלוי בהגדרות.
בניית פרוייקט עם Spring MVC, Hibernate, Maven
בפוסט זה אדגים כיצד לבנות פרוייקט end-to-end בטכנולוגיות: Spring MVC, Hibernate קומפילציה באמצעות Maven והרצה כWAR על Tomcat או כל שרת אפליקציות ג'אווה אחר. הפרוייקט יכלול בסיס נתונים MySQL עם טבלה השומרת פרטי לקוחות, דף JSP שמציג נתונים מהטבלה, שרות REST מבוסס Spring MVC לקבלה ושליחת נתונים ופעולות מול הDatabase באמצעות Hibernate.
ראשית נקים את התשתיות באמצעות הגדרת הpom.xml של הMaven. למען הנוחות, הפרוייקט יכלול מודול יחיד עם כל הקוד. מומלץ לפצל פרוייקטים אמיתיים למספר מודולים כמו: persistence, dao, web וכו'.
לגרסה האנגלית: Maven, Spring, Hibernate Web Tutorial
להלן הקוד של הpom.xml:
<!--?xml version="1.0"?--> <project xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelversion>4.0.0</modelversion> <repositories> <repository> <id>jboss-public-repository-group</id> <name>JBoss Public Repository Group</name> <url>https://repository.jboss.org/nexus/content/repositories/releases/</url> </repository> <repository> <id>SpringSource Enterprise Bundle Repository – External Bundle Milestones</id> <url>http://repository.springsource.com/maven/bundles/milestone</url> </repository> <repository> <id>SpringSource Enterprise Bundle Repository – SpringSource Bundle Releases</id> <url>http://repository.springsource.com/maven/bundles/release</url> </repository> <repository> <id>SpringSource Enterprise Bundle Repository – External Bundle Releases</id> <url>http://repository.springsource.com/maven/bundles/external</url> </repository> <repository> <id>Maven</id> <url>http://repo1.maven.org/maven2/</url> </repository> </repositories> <groupid>il.co.megasoft</groupid> <artifactid>spring-seminar</artifactid> <packaging>war</packaging> <version>app</version> <name>spring-seminar</name> <url>http://maven.apache.org</url> <properties> <maven.compiler.source>1.5</maven.compiler.source> <maven.compiler.target>1.5</maven.compiler.target> <webappdirectory>${project.build.directory}/${project.build.finalName}</webappdirectory> <project.build.sourceencoding>UTF-8</project.build.sourceencoding> <hibernate.version>3.6.0.Final</hibernate.version> <org.springframework.version>3.0.5.RELEASE</org.springframework.version> </properties> <dependencies> <dependency> <groupid>junit</groupid> <artifactid>junit</artifactid> <version>4.8.1</version> <scope>test</scope> </dependency> <dependency> <groupid>org.apache.openejb</groupid> <artifactid>commons-dbcp-all</artifactid> <version>1.3</version> </dependency> <dependency> <groupid>taglibs</groupid> <artifactid>standard</artifactid> <version>1.1.2</version> </dependency> <dependency> <groupid>javax.servlet</groupid> <artifactid>jstl</artifactid> <version>1.1.2</version> </dependency> <dependency> <groupid>net.sf.ehcache</groupid> <artifactid>ehcache</artifactid> <version>[2.0.0,]</version> <type>pom</type> </dependency> <dependency> <groupid>org.hibernate</groupid> <artifactid>hibernate-c3p0</artifactid> <version>3.3.1.GA</version> </dependency> <dependency> <groupid>com.google.gwt</groupid> <artifactid>gwt-servlet</artifactid> <version>2.2.0</version> <scope>runtime</scope> </dependency> <dependency> <groupid>com.google.gwt</groupid> <artifactid>gwt-user</artifactid> <version>2.2.0</version> <scope>provided</scope> </dependency> <dependency> <groupid>commons-lang</groupid> <artifactid>commons-lang</artifactid> <version>2.6</version> </dependency> <dependency> <groupid>org.hibernate</groupid> <artifactid>hibernate-core</artifactid> <version>${hibernate.version}</version> </dependency> <dependency> <groupid>org.hibernate</groupid> <artifactid>hibernate-entitymanager</artifactid> <version>${hibernate.version}</version> </dependency> <dependency> <groupid>org.hibernate</groupid> <artifactid>hibernate-validator</artifactid> <version>4.1.0.Final</version> </dependency> <dependency> <groupid>mysql</groupid> <artifactid>mysql-connector-java</artifactid> <version>5.1.16</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-context</artifactid> <version>3.0.0.RELEASE</version> <scope>runtime</scope> <exclusions> <exclusion> <groupid>commons-logging</groupid> <artifactid>commons-logging</artifactid> </exclusion> </exclusions> </dependency> <dependency> <groupid>org.slf4j</groupid> <artifactid>jcl-over-slf4j</artifactid> <version>1.5.8</version> <scope>runtime</scope> </dependency> <dependency> <groupid>org.slf4j</groupid> <artifactid>slf4j-api</artifactid> <version>1.5.8</version> <scope>runtime</scope> </dependency> <dependency> <groupid>org.slf4j</groupid> <artifactid>slf4j-log4j12</artifactid> <version>1.5.8</version> <scope>runtime</scope> </dependency> <dependency> <groupid>log4j</groupid> <artifactid>log4j</artifactid> <version>1.2.14</version> </dependency> <!-- Hibernate uses slf4j for logging, for our purposes here use the simple backend --> <dependency> <groupid>org.slf4j</groupid> <artifactid>slf4j-simple</artifactid> <version>1.5.3</version> </dependency> <!-- Hibernate gives you a choice of bytecode providers between cglib and javassist --> <dependency> <groupid>javassist</groupid> <artifactid>javassist</artifactid> <version>3.8.0.GA</version> </dependency> <dependency> <groupid>javax.transaction</groupid> <artifactid>jta</artifactid> <version>1.1</version> </dependency> <dependency> <groupid>javax.transaction</groupid> <artifactid>transaction-api</artifactid> <version>1.1</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-core</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-beans</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-aop</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-context</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>org.springframework.oxm</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>org.springframework.web.servlet</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-context-support</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>com.google.collections</groupid> <artifactid>google-collections</artifactid> <version>1.0-rc4</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-tx</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-jdbc</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-orm</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-web</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-webmvc</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-test</artifactid> <version>${org.springframework.version}</version> <scope>test</scope> </dependency> <dependency> <groupid>org.springframework.security</groupid> <artifactid>spring-security-parent</artifactid> <version>${org.springframework.version}</version> <type>pom</type> </dependency> <dependency> <groupid>org.springframework.security</groupid> <artifactid>spring-security-config</artifactid> <version>${org.springframework.version}</version> </dependency> <dependency> <groupid>org.springframework.security</groupid> <artifactid>org.springframework.security.web</artifactid> <version>3.0.3.RELEASE</version> </dependency> <dependency> <groupid>commons-fileupload</groupid> <artifactid>commons-fileupload</artifactid> <version>1.2.2</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupid>commons-io</groupid> <artifactid>commons-io</artifactid> <version>1.4</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupid>org.apache.poi</groupid> <artifactid>poi</artifactid> <version>3.8-beta3</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupid>org.apache.poi</groupid> <artifactid>poi</artifactid> <version>3.8-beta4</version> <type>jar</type> <scope>compile</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupid>org.apache.maven.plugins</groupid> <artifactid>maven-war-plugin</artifactid> <version>2.1.1</version> <executions> <execution> <phase>compile</phase> <goals> <goal>exploded</goal> </goals> </execution> </executions> <configuration> <webappdirectory>${webappDirectory}</webappdirectory> <warsourcedirectory>WebContent</warsourcedirectory> </configuration> </plugin> </plugins> </build> </project>
העדפותיי האישיות לחבילת הג'אווה המושלמת
אז מה הjava stack המועדף עליי?
במהלך עבודתי כיועץ JAVA פרילנסר עצמאי, אני מתבקש לעיתים קרובות להמליץ על טכנולוגיות ג'אווה מתאימות לפרויקט מסויים. בג'אווה בניגוד לטכנולוגיות של מיקרוסופט כמו דוט נט, יש מגוון עצום של אפשרויות בבואינו לבחור סביבות עבודה. לכל שכבה באפליקציה יש אינספור פריימוורקים, ספריות וAPI, חלקם הגדול בקוד פתוח ואחרות מסחריות. כל ארכיטקט ויועץ בוחר בSTACK האהוב עליו בהתאם להעדפותיו האישיות וכמובן בהתחשב בדרישות הפרוייקט וביכולות המוצרים השונים. בפוסט זה אפרוס את ההעדפות האישיות שלי שמתאימות למרבית הפרוייקטים אם כי זה כמובן משתנה בהתאם לצרכי הלקוח.
תצורה כללית Best of bread – Non J2EE
J2EE היא טכנולוגיה טובה אבל במקרים רבים מדובר בover kill והיא עמוסה פיצ'רים ושכבות שלא נמצאות בשימוש ומכבידות על המערכת. השימוש בsession Beans למשל כשהרבה פעמים אין צורך בכך ובכלל הEJB Container המסורבל כאשר הפונקציונליות היחידה שבאמת צריך היא JPA שאפשר לקבל גם ללא EJB (באמצעות Hibernate למשל). בנוסף, בJ2EE יש נטייה להצמד לשרת של יצרן מסויים ודי מהר מתחילים להשתמש בפיצ'רים היחודיים לאותו מוצר. זה גורם לאפליקציה להיות Non Portable כשרוצים לעבור לשרת אחר. פעמים רבות נתקלתי בפרוייקטים שרצים מעל J2EE כשהשרת משמש ללא יותר מאשר Servlet Container. הפלטפורמה של JEE מסבך דברים פשוטים: WAR פשוט הופך לEAR מורכב, Deployment Descriptiors מיותרים בחלק מהגרסאות, ובגלל שהשרתים בדרך כלל תפורים מראש קשה להחליף מודול ספציפי במקרה הצורך (למשל לשדרג לגרסה חדשה יותר). לדעתי כדאי להמנע ככל שאפשר משימוש
Application Server – Apache Tomcat
שרת האפליקציות וServlet Container הנפוץ בעולם. היחיד שיכול אולי להתחרות בו הוא Jetty אבל התיעוד הרב, וותק רב השנים, האינטגרציה הטבעית עם שרת Apache כשרת WEB, והאמינות של ארגון Apache הופכים אותו לאופציה הטובה ביותר.
ORM Persistence – Hibernate JPA Annotations
למרות שעדיין יש ארגונים המפתחים בhibernate בתוצרה הישנה קרי, שימוש בXML על מנת להגדיר את המיפוי בין המחלקות לטבלאות, השימוש בJPA annotations מהווה יתרון מכמה סיבות: ראשית זה מאפשר מעבר לEJB במידה ורוצים בעתיד להעביר את האפליקציה לJEE. שנית, הקוד הרבה יותר קריא וברור כשמעל לכל Property בקוד מופיע הגדרות המיפוי שלו. כמו כן זה מוריד את הצורך לתחזק XML שבמקרים רבים הופך להיות ארוך ומסורבל.
Dependency Injection – SPRING
במקומות רבים מוותרים לגמרי על השימוש בDI ובSPRING בפרט אבל לדעתי זהו פיצ'ר חשוב שמייעל ומפשט את הקוד בצורה משמעותית. הבחירה שלי בSPRING ולא בפריימוורקים אחרים כגון Google Guise היא שSPRING הוא הרבה יותר מרק DI. במקרה הצורך אפשר למצוא מענה טוב לכמעט כל טכנולוגיית ג'אווה קיימת: JMS, JPA, Security, RMI, Web Service, מימוש טוב לAOP ועוד ועוד. היופי בSPRING הוא היותו מבוסס על POJO כך שתמיד קל להבין איך דברים עובדים והכי חשוב: אפשר לשלוט בכמות הSPRING שמשתמשים בו בקוד. החל משימוש מועט רק בDI וכלה באפליקציית SPRING מלאה כולל MVC ושאר הפיצ'רים שהפריימוורק הענק הזה מציע.
UI – Rich Client with Adobe Flex
זה אמנם לא ג'אווה ולא WEB קלאסי אבל כיום אין תחליף לפלקס וליכולות שהוא מציע באפליקציות שדורשות קליינט מורכב וברמה גבוהה. יש כמה בעיות בUI רגיל מבוסס HTML וAJAX: ראשית הקליינט לא אמין. Exception יכול לגרום לכל הדף לעוף מבלי יכולת שחזור, ניתוק זמני מהשרת גורם לעיתים קרובות לאיבוד מידע, בעיית תאימות בין דפדפנים, קושי לפתח אלמנטים גרפיים מורכבים, ועוד. שורש הבעייה בממשקי WEB נעוץ בעובדה שהממשק בסופו של דבר בנוי על HTML. הבעייה ששפה זו מלבד היותה מיושנת, יועדה במקור ליצירת מסמכים עשירים ולא לממשקי משתמש. אין גרפיקה ווקטורית, אין קומפוננטות UI נפוצות כמו Tree והקומפוננטות הקיימות מספקות פונקציונליות שמתאימה למסמך ולא לאפליקציה. קחו למשל את Table, על מנת שהוא ייראה כמו טבלה מודרנית עם אפשרות להזזת עמודות, מיונים, פילטרים וכו' צריך לכתוב קוד רב ובדרך כלל להשתמש בכלל באלמנטים אחרים כמו DIV ולא בTABLE של HTML. הפריימוורקים הרבים הקיימים מבוססים על קוד JavaScript שמבצע מניפולציות על אלמנטים בHTML ו"אונסים" אותם כך שייראו כמו UI נורמלי. הדבר היחיד שיכול לשנות את התמונה הוא HTML5 אבל יש עוד דרך ארוכה עד שנוכל להשתמש בתקן זה. ראשית צריך שהתקן יסגר ויתמך בכל הדפדפנים, אחר כך צריך שחברות יפתחו ספריות קומפוננטות מבוססות HTML5. הcanvas כנראה יספק בסיס מתאים אבל מישהו צריך לפתח ולפרסם פקדים שיהיה ניתן לעבוד איתם. כך שבנתיים מבחינת טכנולוגיות זמינות ובוגרות FLEX היא האופציה הטובה ביותר לפיתוח מהיר של ממשקים גרפיים עשירים.
שמירה על סדר מיון בFLEX DataGrid
בעייה מעצבנת שקוראת בDataGrid של פלקס. כאשר ממיינים לפי עמודה ולאחר מכן מחליפים את הDataProvider, הסדר לא נשמר. כלומר המיון מתבצע רק בלחיצה על הכותרת אבל לא מתבצע שוב כשהprivder משתנה (מוחלף ליתר דיוק). כדי לעקוף זאת, כתבתי class נחמד שיורש מDataGrid ומטפל בבעייה. להלן הקוד:
package il.co.j2ee.samples { import mx.controls.DataGrid; import mx.events.CollectionEvent; import mx.events.DataGridEvent; import mx.events.FlexEvent; public class SortedDataGrid extends DataGrid { private var lastSortingEvent:DataGridEvent; public function SortedDataGrid() { super(); addEventListener("headerRelease", onHeaderRelease); } public override function set dataProvider(data:Object):void{ super.dataProvider = data; addEventListener(CollectionEvent.COLLECTION_CHANGE, onDataChange); } private function onHeaderRelease(event:DataGridEvent):void{ lastSortingEvent = event; } private function onDataChange(event:CollectionEvent):void{ if (lastSortingEvent){ dispatchEvent(lastSortingEvent); } } } }
כך זה נראה במקור:
וכך לאחר השימוש בSortedDataGrid:
שליטה על כפתורי הדפדפן (Browser) בFLEX
לעיתים, יש צורך למנוע מהמשתמש לצאת מהאפליקציה בו הוא נמצא או להזהיר אותו כשהוא עומד לעזוב את הדף. הבעיה שבפלקס דברים רבים נשמרים בClient ומשתמשים לא תמיד מודעים לכך שסגירת הדפדפן עלול להביא לאיבוד מידע. הקוד הבא מדגים כיצד ניתן להזהיר את המשתמש בעת לחיצה על Back, Forward, Refresh או סגירת הדפדפן (או הלשונית).
הקוד מבוסס ג'אווה סקריפט (JS) ויכול לשמש גם באפליקציות WEB רגילות.
הערה: הקוד נבדק ונמצא תומך על הדפדפנים הבאים:
- IE 7 ומעלה
- FireFox
- Chrome
קוד הJavaScript שיש לכלול בדף שעוטף את הSWF:
<script type="text/javascript"> var url = window.location; window.onhashchange = locationHashChanged; window.onbeforeunload = function(){ return "All unsaved changes will be lost" } function locationHashChanged(){ var currentUrl = window.location.href; if (currentUrl.indexOf('#c') == -1){ var answer = confirm("Are you sure you want to leave this page? All unsave changes will be lost"); if (answer){ history.go(-1); } else{ window.location = url+"#c"; } } } </script>דוגמא מלאה אפשר לראות כאן
החרם על מוצרים בשטחים הוא דווקא סימן מעודד
הכותרות האחרונות בדבר החרם הפלסטיני על מוצרים מהשטחים מעוררים תגובות נזעמות בישראל. בישראל טוענים בלהט שהחרם יפגע דווקא בפועלים הפלסטינים המועסקים בהתנחלויות ואילו ברשות מגיבים בתוכנית תעסוקה למובטלים הפלסטינים. אבל דווקא החרם הזה גם אם הוא טומן בחובו נזק כלכלי כלשהו, מהווה אבן דרך וסימן חיובי ליחסי ישראל והפלסטינים. לראשונה מזה זמן רב מוצאים הפלסטינים דרכים אחרות להביע את עמדותיהם ולהתנגד למדיניות הישראלית. דרכים שאינם כוללים מעשי רצח וטרור ואלימות במגוון סוגים. סוף סוף אולי צומח כאן שיח אזרחי חדש שמראה שאפשר להאבק ולא להסכים גם מבלי לרצוח זה את זה. חרם צרכנים הוא כלי לגיטימי והגיוני ודרך טובה להביע התנגדות למה שמכונה "הכיבוש" בניגוד לירי, השלכת בקבוקי תבערה או ידוי אבנים על רכבים ישראלים.פתאום מדברים על "מאבק עממי לא אלים", "סקטורים עסקיים וציבוריים" ועוד מושגים שכבר מזמן לא נשמעו מבני דודינו.
לכן על ממשלת ישראל להגיב בזהירות לצעד זה. הקריאות ל"סגירת נמלי האוויר והים" כפי שקראו ח"כים בימין יוכיחו לפלסטינים שישראל אכן הינה כובשת אכזרית ושאלימות וטרור היא הדרך היחידה. אל לישראל לנצל לרעה את עצמתה במיוחד לאור המצב הלא שוויוני. מאידך, גם התעלמות מהחרם עלולה להביא לתוצאה לא רצוייה. אם החרם לא מעניין אף אחד, כנראה שהחמאס צודק וצריך לחזור לאלימות. הדרך הנכונה תהיה לאותת שהחרם דווקא מזיז לישראל, מתייחסים אליו פה ברצינות ומוכנים גם לנהל מו"מ סביב הנושא.