Implements bridge pattern with wcf
באחד מהפרויקטים שעבדתי עליהם לאחרונה היתה דרישה להחליף תוכנת עמדה במערכת מבזורת בתוכנה אחרת .
התקשורת בין כל העמדות הינה WCF ,כאשר הcontract של העמדה החדשה שונה לחלוטין מהcontract הקיים ואין אפשרות להחליפו לזה שהיה קיים .(העמדה הייתה ונשארה server )
ל Contract החדש אין תאימות חד חד ערכית ברמת הקריאות כלומר חלק מהקריאות של ה Client היו צריכות מספר קריאות בContract של ה Server החדש חלק היינו צריכים לשמור ב Cache על מנת לאגד מספר קריאות מה client לקריאה אחת בServer וכד.

חשבנו על 3 פתרונות אפשריים
פתרון 1 מערכת חיצונית
לכתוב או לקנות מערכת חיצונית שהינה מאין biztalk sever אשר יבצע את פעולת התרגום .
יתרונות
מבחינת decoupling מאוד גבוה בין מנגנון ההמרה לאפליקציה , השינוי היחיד שנדרש לבצעו באפליקציה זה החלפת ה address של ה wcf server .
אפשרות ניטור של המידע הזורם + העבודה המתבצעת באותו מתאם .
תמיכה יכולת גידול לעתיד לdevice ים או שיטות תקשורת שונות .
חסרונות
הבעיה שצריך לתחזק עוד אפליקציה \ service שאיננו חלק אינטגרלי מהאפליקציה .(תיעוד התקנה קיטלוג )
לקנות אפליקציה חיצונית עולה כסף .
Hop נוסף של מידע שאין בו צורך . המידע נשלח ל wcf service ולאחריו ל עמדה הנוספת.
פתרון 2 השיטה הסינית
שיטת הquick and drity לאתר את כל המקומות בקוד שפונים לwcf client ולשתול בכל המקומות switch ים של האם לשלוח בintrerface החדש או הישן . + להוסיף את כל הלוגיקה בתוכנה עצמה של ה Client .
יתרונות
למרות שיש הרבה מאוד עבודה העבודה פשוטה אין סיכון טכנולוגי .
חסרונות
מכיוון שבאפליקציה אין שיכבה אחת מסודרת שקראה לwcf clients אלא הקריאות התבצעו מכל מקום אפשרי וישנם מאות מקומות כאילו הקוד של האפליקציה יסרבל עוד יותר כי זה לא רק הקריאה עצמה יש להוסיף לוגיקה לכל קריאה .
כמובן שאפשר לעשות את זה יותר אלגנטי ולא להשתמש בSwitch אלא שימוש ב Factory שיחזיר Iterface ל ממשק .
הפתרון ה 3 Wcf custom channel
פתרון באמצעות Custom protocol channel שכתבנו שתפקידו לתרגם את ההודעות .
יתרונות
פתרון שהינו מאוד decoupled אין צורך לפתוח אף אחד מהתוכנות . אפשר באמצעות קונפיגורציה להכיל אותו .
לא נוצר עוד hop מיותר
מאתגר מקצועית
אפשר באמצעותו ללמוד wcf לעומק
מאוד מרשים ראשי צוותים סמי מיקצועיים (כאילו שיועדים מה זה wcf בשתי מילים אבל אין להם שמץ ניסיון בארכיטקטורה נכונה וכד )שאתה מספר להם ברעיון עבודה שעשית כזה דבר חושבים איזה ילד פלא אתה ,השועלים הותיקים לא יתנו לך לסיים את הטרילוגיה והם כבר יראו לך את הדלת. שתסבך עד מוות את הפרויקט של מישהו אחר .
חסרונות
מאוד technology aware כלומר מבוסס על יכולות ההרחבה של wcf ותלוי ביכולות האלו . ומכיוון ש wcf איינו קוד פתוח ניתן להיתקע עם memory leak בעיית ביצועים או חלקים שבלתי ניתנים למימוש .
Wcf channels
התשתית של wcf מכילה ערימה של channels המתפקדים כ chain of responsibility
דומה מאוד ל ndis filter drivers .

בין כל החלקים של ה channel זורמים אוביקטים מסוג Message .
הMessage
מורכב מ Headers וPayload של המידע (הAction והפרמטרים שלה ).
כל channel יכול להוריד להוסיף או לשנות את ה header ים .
כמו כן ה Channel יכול לשנות את ה payload כלומר את המידע עצמו שעובר ב Message
(שינוי פשוט של מידע יכול להתבצע באמצעות Behaviors כדוגמת מימוש IOperationBehavior ביתר קלות )
הפתרון שלנו התבסס על זה ש Channel במקרים מסוימים יכול להחליט שהוא לא מעביר הלאה הודעה (כלומר חוסם אותה ) או מעביר כמה הודעות במקומה ) או מעביר כמה הודעות בזמנו החופשי בthread אחר אחרי שההודעה בכלל נסתיימה .
הפתרון הזה עובד טוב רק ב interface אסינכרוני .בinterface סנכרוני המצב מסתבך כי המערכת מנסה להחזיר לך תמיד הודעה על כל קריאה באופן סינכרוני ויצירת מצב ששתי הודעות נשלחות ומקבלים רק תשובה אחת מאוד בעיתי , אינני יודע אם אפשרי בכלל.
אנו כתבנו Custom channel משלנו והכנסנו אותו ל channel stack .

ה Channel הכיל את הלוגיקה כלומר את המימוש של ה Bridge שמתאם בין ההודעות , הchannel הינו statefull הוא זכר את כל המידע מהקריאות הקודמות שדרושות לצורך התאום .
הפיתרון מכיל את ה class ים הבאים :

LogicOrcBinding
הcustom binding שמכיל את ה binding elements שלנו .

הגדרנו רק 2 elements כאשר תשתיות ה wcf מוסיפות את החסר בהתאם לקונפיגורציה אם זה security , relaibale session וכד
התשתית מוסיפה את שאר ה elements במקומות הנכונים כך שהצפנה לדוגמה תתבצע אחרי הטיפול שלנו .
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
LogicOrcBindingElement , LogicOrcChannelFactory , LogicOrcChannelListener
מכילים את ה בוילרפארט קוד בד"כ לא נוגעים או משנים אותם. תפקידם לעטוף את היצירה של ה channel שלנו .
כאשר יש פרמטרים שצריך להעביר מה Binding עצמו ,צריך לדאוג להעביר דרכם .
LogicOrcBodyWriter
class עזר פשוט שתפקדו לעזור בלכתוב את ה פרמטרים של הAction בMessage
הClass מכיל event בשם writeBodyCallback שנקרא אוטומטית על ידי המערכת לצורך עדכון הפרמטרים .

LogicOrcChannel
זהו לב המערכת

בCtor אנו מקבלים Reference ל Channel הבא בstack

אנו נשתמש בReference הזה לשני דברים :
1.להפנות מימוש methods למימוש ב Channel הבאה בstack ב methods שאין לנו צורך לממשן
לדוגמא:

2.הפניית הMessage אחרי הטיפול לChannel הבאה ב stack
הmethods שמטפלות בשליחת הנתונים ל channel הבאה בstack הינן :
public void Send(Message message, TimeSpan timeout) ,
public IAsyncResult BeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state)
כאן משתלבת הלוגיקה העסקית של הBridge בשליחה של הודעות לServer ,ניתן לא להעביר את הMessage ל channel הבא ובכך לחסום את ההודעה ניתן להעביר הודעה אחרת או מספר הודעות אחרות ,כמו כן ניתן להעביר את הטיפול ל thread אחר ובזמן שונה להעביר הודעה ל Channel הבא ב Stack .

דוגמא למימוש
פונקצית עזר לבניית Message חדש בהסתמך על העתקת ה Header של ה message שהתקבל מבChannel הקודם ושינוי ה Action

הפונקציה הבאה מקבלת פקודה אחת ומפצלת אותה ל שניים

הMethods שמטפלות בקריאת הנתונים בצורה סינכרונית או אסינכרונית בהתאמה הינן :
public bool TryReceive(TimeSpan timeout, out Message message)
והצמד
public
IAsyncResult BeginTryReceive(TimeSpan timeout, AsyncCallback callback, object state)
public bool EndTryReceive(IAsyncResult result, out Message message)
אחרי ששלחנו שתי הודעות Call1 וCall2 ל Client אמו מקבלים גם שתי תשובות אותן אנו צריכים לאחד לתשובה אחת .את הפעולה הזו אנו עושים ב EndTryReceive

Exception
בזמן הפיתוח אם מבצעים שגיאה בקריאה של ה Contract לדוגמא קוראים ל Action שאיננו נמצא ב contrat של ה Server המערכת נותנת שגיאה מפורטת :
{"The message with Action 'http://tempuri.org/ILogicOrcSample/CallWithWrongAction' cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver. Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None)."}
טעיות בשמות של ה פרמטרים בד"כ יגרמו ל להעברת null בפרמטרים של ה method
בדיקות
שתי בדיקות נעשו לצורך ולידציה של הפתרון :
1.בדיקת memory leak שהמערכת לא משאירה אצלה זבל במקרה ונחסמת פקודה מלהגיע ליעדה וכד.על מנת לבצע את הבדיקות עם התוכנית דוגמא יש להוריד את כל ה חלק של ה Diagnostic מה Configuration files , לבצע את הקריאות הנדרשות בloop ומידי פעם לקרא ל GC.Collect
2.בדיקת ביצועים ש ה Bridge Channel לא פוגע בביצועים כתוצאה מביצוע הלוגיקה שלו
בבדיקה שבצעתי לא נראה כל דליפת זיכרון אך אני ממליץ בחום לכל מי שחושב על הפתרון לבדוק גם את נושא הזיכרון , יציבות , ביצועים של הקונספט האם הם מתאימים לדרישותיו.
קישור לקוד דוגמא:
קוד דוגמא מאוד בסיסי נמצא כאן .
הקוד מבוסס על דוגמא של מיקרוסופט בשם Chunking channel לדוגמא הוכנסו רק הדברים האלמנטריים ביותר לצורך הדגמת הקונספט .
על מנת להשתמש בה יש כמוסן להוסיף לה את כל הקוד הסטנדרטי כגון טיפול ב Timeouts סגירת Channels ללא exceptions וכד.