ארכיון

ארכיון של מרץ, 2010

SMS Integration: Israel is wayyy behind US

והפעם פוסט סביב אינטגרציה.

החלטנו להוסיף למוצר שלנו מערכת התראות ב SMS. הנמענים אמורים להיות בישראל, בלי מגבלה על הרשתות והמפעילים.
האינסטינקט הראשוני הוא ליצור קשר עם ספקי SMS בישראל, שמאפשרים התממשקות באמצעות Web Service כזה או אחר, להתרשם ולקנות חבילה.
גיגלתי ומצאתי שני ספקים ישראליים:

  • SimpleSMS
  • שירות של גולדמן תקשורת שנקרא SMS API

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

אז כאמור, גולדמן תקשורת. שם זה סיפור אחר: באתר שלהם מדברים על API בצורת Web Service, ואפילו יש screenshot שממחיש את זה, אבל תיעוד אמיתי אין. ודוגמאות קוד, זה בכלל "מוקצה". האמת, חייבים להיכנס ולראות כדי להאמין.
אבל אני, אופטימיסט אני. אמרתי, ניתן צ'אנס, נרים טלפון. התקשרתי ובקשתי לנסות את המוצר, או לפחות לקבל את ה API הנכסף (ולא ב screenshot). מהעבר השני של הקו הסביר לי, בתקיפות לא ברורה, שהם היחידים שעובדים דרך סלקום, כבר שנים, ושהמתחרים לא אמינים כמותם. הסיטואציה לא היתה מספיק ברורה (לשנינו, כנראה), וסיכמנו שכדאי לי לדבר עם אסף, הבן שלו, בעניין ה API.
מסתבר שתחום ה pre sales בגולדמן תקשורת קצת בעייתי, כי אסף סרב לספק את התיעוד ל API, והסביר שאני צריך לקנות את השירות כדי לקבל את ה API והתיעוד. עכשיו, נכון שהחבילה המינימלית עולה בסה"כ 20 ש"ח, וזה סכום פעוט לחברות תוכנה, אבל למה להחביא את ה API? מה כל כך סודי בו? אסף הסביר לי שמנסיון העבר שיש להם, זו מדיניות החברה.
יש משהו קצת פישי בהסתרת API, ולא בעיה להרים שירות Dummy שיהווה "מתקן אימונים" אם רוצים להסתיר את הכתובת של השירות האמיתי. במילים אחרות, אני לא רואה בעיה שהיא לא פתירה, ובגלל זה אני מסיק שיש כאן בעיה של מקצועיות.
אז גם גולדמן תקשורת נפסל.

באותו זמן, ומהעבר השני של החדר, כלומר במרחק של מטר וחצי ממני, רשף כבר נרשם לשירות אמריקאי שנקרא Twilio. הם שולחים הודעות SMS להרבה מקומות בעולם, גם לישראל. תוך חצי שעה כבר נרשמנו, שילמנו (עם סליקה מוצלחת!), והיה לנו קוד מלא ועובד.

נחזור רגע לאינסטינקט הראשוני (מתחילת הפוסט), שאומר שבשביל שירות אינטגרציה מקומי כדאי לפנות לספקים מקומיים, כי הם בטח מתמצאים יותר ויהיו דרכם פחות בעיות טכניות. מסתבר שהפעם, פניה לספק חיצוני העלתה תוצאה טובה יותר: האמריקאים כנראה יודעים לעשות את זה טוב יותר ומקצועי יותר מהישראלים (ובכמות גדולה של הודעות, גם זול יותר). אולי כי הסליקה שלהם עובדת בלי בעיה, ואולי כי הכל פשוט מתקתק כמו שצריך, עם שירות מבוסס ReST, ועם תיעוד מלא ומקיף (ולא מוסתר…).
זה אולי לא מדגם מייצג, אבל בכל זאת, בשנת 2010, הייתי מצפה מחברות טכנולוגיה/אינטגרציה מקומיות להרים שירות בסטנדרט גבוה יותר.

לא נורא, העיקר שעובד 🙂

קטגוריות:אינטגרציה, תכנות תגיות:,

Startable – State Machine part 1

בפוסט הזה:

  • קצת על State Machine
  • Startable State Machine
  • מתי משתמשים
  • מימוש רגיל
  • הפרדת interface מהמימוש
  • מימוש עם נעילות
  • הדגמה פשוטה

בפוסט הבא אני אראה איך אפשר להשתמש ב state machine הזה (בירושה, למשל)

קצת על State Machine

חלק משמעותי מעולם התכנות כולל מעבר ממצב x למצב y, כאשר כל המצבים ידועים מראש. אפשר לומר שכל התוכניות שכתבתי עד היום הן כאלו. אמנם,לא תמיד ידעתי לצפות מראש את כל המצבים, ופה ושם היו באגים שבדרכם גרמו לי להבין שיש עוד מצב או שניים שצריך לקחת בחשבון, אבל בסופו של דבר, זו היתה קבוצה סגורה ומוגדרת היטב של מצבים.

קבוצת המצבים הזו יכולה להיות מתוארת כמכונת מצבים (סופית), או באנגלית Finite State Machine, ובקיצור FSM. המעבר ממצב למצב נקרא transition ובד"כ הצורך לעבור ממצב אחד לאחר נובע מקלט כזה או אחר, או הודעה פנימית. אפשר לקרוא עוד בויקיפדיה (עברית ואנגלית) כדי להעמיק.

Startable State Machine

תרשו לי להציג מכונת מצבים פשוטה ונחמדה. אולי פשוטה מדי, אבל אני מקווה שאצליח לשכנע שהיא נחוצה ומעודדת שימוש חוזר בקוד (שזו מטרה נאה לכל הדעות). אני קורא למצבים פשוטים כאלה "mini patterns". לא ממש design pattern, אבל בהחלט מעודד שימוש חוזר בקוד.

מדובר בסה"כ בשני מצבים אפשריים: Started ו Stopped. או בעברית: מופעל/מופסק.

נדמיין שני לחצנים: לחצן Start ולחצן Stop (כמו Play ו Stop במכשיר DVD, למשל)

אם המצב הנוכחי הוא Stopped, ולחצנו על Start – עוברים למצב Started. כל לחיצה נוספת על Start לא משנה את המצב.

וכנ"ל להיפך: אם המצב הנוכחי הוא Started, ולחצנו על Stop – עוברים למצב Stopped. כל לחיצה נוספת על Stop לא משנה את המצב.

לצורך העניין, מצב הפתיחה הוא Stopped.

התרשים הבא יכול לתאר את זה:

Startable State Machine - chart

מתי משתמשים?

השימוש העיקרי שלי ב Startable הוא כשבתוך אפליקציה קיימת, אני צריך איזשהו שירות שיהיה פעיל ברקע כל הזמן, או פעיל לפעמים. למשל: שרת UDP שמאזין להודעות נכנסות בפורט מסויים, סטרימר ששולח Streaming Video למחשב אחר, רכיב שבודק כל X שניות מה הסטטוס של רכיב אחר, וכו' וכו'. לכל רכיב שכזה יש בד"כ שני מצבים: "פעיל" או "לא פעיל". קלאסי ל Startable.

מימוש רגיל

המימוש הוא די פשוט:

namespace Groundhog.Lib
{
    public enum StartableStatus
    {
        Started,
        Stopped
    }

    public class StartableStateMachine
    {
        private StartableStatus status;

        public event Action OnStart;
        public event Action OnStop;

        public StartableStateMachine() : this(StartableStatus.Stopped)
        {
        }

        public StartableStateMachine(StartableStatus initialStatus)
        {
            status = initialStatus;
        }

        private void InvokeAction(Action e)
        {
            if (null == e)
                return;
            e();
        }

        private void InvokeOnStop()
        {
            InvokeAction(OnStop);
        }

        private void InvokeOnStart()
        {
            InvokeAction(OnStart);
        }

        public void Start()
        {
            if (status == StartableStatus.Started)
                return;

            // it was "stopped"
            // change to "started"            
            status = StartableStatus.Started;
            // and raise the proper event
            InvokeOnStart();
        }

        public void Stop()
        {
            if (status == StartableStatus.Stopped)
                return;

            // it was "started"
            // change to "stopped"            
            status = StartableStatus.Stopped;
            // and raise the proper event
            InvokeOnStop();
        }

        public StartableStatus Status
        {
            get
            {
                return status;
            }
        }        
    }
}

סקירת קוד קצרה:

  • יש כאן enum על הסטטוס: Started או Stopped
  • אפשר לקבל את הסטטוס הנוכחי באמצעות המאפיין Status (דה!)
  • בנוסף יש שתי מתודות: Start ו Stop שהן ה"לב" של ה state machine שלנו
  • וכדי להודיע לעולם שהסטטוס השתנה, יש שני אירועים: OnStart ו OnStop.

מה אין כאן?
הקוד הוא לא thread-safe, מיד נגיע לזה.

הפרדת interface מהמימוש

ממוש, תפריד לי את ה interface מהמימוש, טוב?
הנה, מותק:

namespace Groundhog.Lib
{
  public interface IStartableStateMachine
  {
    event Action OnStart;
    event Action OnStop;
    void Start();
    StartableStatus Status { get; }
    void Stop();
  }
}

ועכשיו, אחרי שיש לנו הפרדה בין החוזה למימוש, אפשר לממש בצורות אחרות.

מימוש עם נעילות

כדי שהקוד יהיה thread-safe, אפשר:

  • להשתמש בנעילות רגילות (או באבסטרקציה שהצעתי בפוסט אחר)
  • לעבוד עם Interlock, שמהווה מנגנון יותר light-weight לנעילות מהסוג שצריך כאן

כל הקוד לנעילות השונות נמצא בקובץ המצורף. אני לא נכנס לזה פשוט כי זה לא העיקר בפוסט הזה.

הדגמה פשוטה

הנה קוד של Console Application שנותן לנו קצת להרגיש את כל מה שהיה לנו עד עכשיו.

using Groundhog.Lib;
namespace Groundhog.ConsoleApp
{
  class Program
  {
    static void Main(string[] args)
    {
      IStartableStateMachine ssm = new StartableStateMachine();
      ssm.OnStart += MyOnStart;
      ssm.OnStop += MyOnStop;

      Console.WriteLine("===========================");
      Console.WriteLine("simple flow: start and stop");
      Console.WriteLine("===========================");
      Console.WriteLine(ssm.Status);
      ssm.Start();
      Console.WriteLine(ssm.Status);
      ssm.Stop();
      Console.WriteLine(ssm.Status);

      Console.WriteLine();
      Console.WriteLine();
      Console.WriteLine("========================================");
      Console.WriteLine("multiple starts and then multiple stops,");
      Console.WriteLine("should invoke each event just once");
      Console.WriteLine("========================================");
      ssm.Start();
      ssm.Start();
      ssm.Start();
      ssm.Start();
      Console.WriteLine(ssm.Status);
      ssm.Stop();
      ssm.Stop();
      ssm.Stop();
      ssm.Stop();
      ssm.Stop();
      Console.WriteLine(ssm.Status);

      Console.WriteLine();
      Console.WriteLine();
      Console.WriteLine("===================");
      Console.WriteLine("press enter to quit");
      Console.WriteLine("===================");
      Console.ReadLine();
    }

    static void MyOnStart()
    {
      Console.WriteLine("hey, it started!");
    }

    static void MyOnStop()
    {
      Console.WriteLine("dude, this thing stopped");
    }
  }
}

והפלט יהיה:

===========================
simple flow: start and stop
===========================
Stopped
hey, it started!
Started
dude, this thing stopped
Stopped

========================================
multiple starts and then multiple stops,
should invoke each event just once
========================================
hey, it started!
Started
dude, this thing stopped
Stopped

===================
press enter to quit
===================

כפי שניתן לראות, גם אחרי מספר קריאות עוקבות ל start, הסטטוס נשאר started, וכנ"ל גם לגבי קריאות למתודה stop. כלומר יש לנו state machine בדיוק כמו שרצינו.

בהזדמנות הבאה אני אראה איך משתמשים בקוד הזה בירושה (ובדרך נוספת), בדיוק עבור אותם רכיבים שמבצעים איזושהי עבודה ברקע.

תכנות נעים!

כל הקוד בפוסט הזה נמצא כאן.

קטגוריות:תכנות תגיות:,
Quantcast