Understanding a system or software architecture simply, truly, absolutely, undoubtedly requires an occasional sequence diagram.
I left university before object-oriented design became the norm, but it was trending up. As part of my first job we were officially trained in OO and the use of Rational Rose. The tool was integrated into our development process and environment, I embraced that tool, particularly in the ability to depict class interactions via 'message trace diagrams', otherwise known as 'sequence diagrams'. Unfortunately, Rational Rose is pricey and uncommon in today's development practices but sequence diagrams are alive and well. Whiteboarding diagrams is tedious and time-consuming, but the inter-webs has on-line tools that really ease the creation of diagrams; like this fella right here: https://www.websequencediagrams.com/
With a bit of effort, Python paired with ImageMagick can generate a rough approximation.
A quick tool/library can make this:
diagram=Diagram();
obj1=Object('object1');
obj2=Object('object2');
obj3=Object('object3');
m1=Message(obj1,obj2,'method1(arg1)');
diagram.add(m1);
m2=Message(obj2,obj1,'return val');
diagram.add(m2);
m3=Message(obj1,obj1,'method2(abc)');
diagram.add(m3);
m4=Message(obj1,obj3,'method3()');
diagram.add(m4);
diagram.draw();
into this:
The code/library follows:
user@kaylee:~/PyMtd$ cat -n drawMtd
1 #!/usr/bin/python
2 import os;
3
4 def log(S):
5 print "__LOG '%s'"%(S);
6 pass;
7
8 class Diagram:
9 def __init__(self):
10 self.outFile_="./out.jpg";
11 self.width_=0;
12 self.height_=0;
13 self.msgList_=[];
14
15 def add(self, msg):
16 self.msgList_.append(msg);
17
18 def textDim(self, text):
19 cmd="convert label:'%s' %s"%(text,'temp.jpg');
20 os.system(cmd);
21 cmd="identify temp.jpg | cut -f 3 -d ' '";
22 retVal=os.popen(cmd).read();
23 os.system("rm temp.jpg");
24 return retVal.rstrip('\n');
25
26 def draw(self):
27 betweenObj=100;
28 objY=20;
29
30 #--position object, heads of lifelines
31 iW=betweenObj;
32 for msg in self.msgList_:
33 msg.src_.y_=objY;
34 msg.sink_.y_=objY;
35 if msg.src_.x_==0:
36 msg.src_.x_=iW;
37 iW+=betweenObj;
38 if msg.sink_.x_==0:
39 msg.sink_.x_=iW;
40 iW+=betweenObj;
41
42 #--draw message lines
43 cmd="convert ";
44 y=50;
45 rightArrow="l -15,-5 +5,+5 -5,+5 +15,-5 z"
46 leftArrow="l +15,+5 -5,-5 +5,-5 -15,+5 z"
47 for msg in self.msgList_:
48 src=msg.src_;
49 sink=msg.sink_;
50 if src.x_ == sink.x_:
51 W=30;
52 H=20;
53 cmd+="-draw 'line %d, %d %d,%d' "%(src.x_,y, src.x_+W,y);
54 cmd+="-draw 'line %d, %d %d,%d' "%(src.x_+W,y,src.x_+W,y+H);
55 cmd+="-draw 'line %d, %d %d,%d' "%(src.x_+W,y+H,src.x_,y+H);
56 cmd+="-draw \"path \'M %d,%d %s'\" "%(src.x_,y+H,leftArrow);
57 D=self.textDim(msg.label_);
58 tH=int(D.split('x')[0])/2;
59 tW=int(D.split('x')[1])/2;
60 cmd+="-draw 'text %d,%d \"%s\"' "%(src.x_+W+5,y+((tH)/2), msg.label_);
61 y=y+H;
62 else:
63 cmd+= "-draw 'line %d,%d %d,%d' "%(src.x_,y,sink.x_,y);
64 cmd+="-draw \"path \'M %d,%d %s'\" "%(sink.x_,y,(leftArrow if src.x_ > sink.x_ else rightArrow));
65 D=self.textDim(msg.label_);
66 textWidth=int(D.split('x')[0])/2;
67 textHeight=int(D.split('x')[1])/2;
68 cmd+="-draw 'text %d,%d \"%s\"' "%((src.x_+sink.x_)/2-textWidth, y-(textHeight/2), msg.label_);
69 y+=20;
70 self.height_=y+50;
71 self.width_=iW;
72 cmd+="-size %dx%d xc:white -fill none -stroke black "%(self.width_,self.height_);
73
74 #--draw lifeline
75 L=[];
76 for msg in self.msgList_:
77 src=msg.src_;
78 sink=msg.sink_;
79 D=self.textDim(src.name_);
80 w=int(D.split('x')[0])/2;
81 for obj in [src, sink]:
82 draw=not (obj in L);
83 if (draw):
84 cmd+="-draw 'text %d,%d \"%s\"' "%(obj.x_-w,obj.y_-5,obj.name_);
85 cmd+="-draw 'stroke-dasharray 5 5 line %d, %d %d,%d' "%(obj.x_,obj.y_, obj.x_,self.height_-20);
86 L.append(obj);
87
88 cmd+="%s"%(self.outFile_);
89 log(cmd);
90 os.system(cmd);
91
92 class Object:
93 def __init__(self,name):
94 self.name_=name;
95 self.x_=0;
96 self.y_=0;
97
98 class Message:
99 def __init__(self, srcObj, sinkObj,label):
100 self.src_=srcObj;
101 self.sink_=sinkObj;
102 self.label_=label;
103
104
105 def test00():
106 diagram=Diagram();
107 obj1=Object('object1');
108 obj2=Object('object2');
109 obj3=Object('object3');
110
111 m1=Message(obj1,obj2,'method1(arg1)');
112 diagram.add(m1);
113
114 m2=Message(obj2,obj1,'return val');
115 diagram.add(m2);
116
117 m3=Message(obj1,obj1,'method2(abc)');
118 diagram.add(m3);
119
120 m4=Message(obj1,obj3,'method3()');
121 diagram.add(m4);
122
123 diagram.draw();
124
125 def test01():
126 diagram=Diagram();
127 diagram.draw();
128
129 def test02():
130 L=[];
131 for i in range(0,10):
132 L.append(Object('object%d'%i));
133
134 diagram=Diagram();
135 for obj in L:
136 m=Message(L[0],obj,'message x');
137 diagram.add(m);
138 m=Message(L[0],obj,'methodX()');
139 diagram.add(m);
140 diagram.add(Message(obj,obj,'ping'));
141
142 diagram.draw();
143
144 #---main---
145 test00();
146 #test01();
147 #test02();
Pair this with some logging analysis, or debugger traces and you could auto-generate entire system interactions with ease.
Take it and do great things.
This is great in a python-only environment. But plantuml is a great room.
ReplyDeleteIn line 19 of the code, a command string is created referring to a temp.jpg. How is this image created? the convert command doesn't seem happy with the arguments passed to it
ReplyDeleteThe 'temp.jpg' is the created image file. The command results in the form: "convert label:'SomeLabel' temp.jpg", which means 'create a text label SomeLabel and store it in a JPG image file called temp.jpg. Home that helps.
DeleteGates could be regarded as being a simple way to model the passing of data between a sequence diagram and its context. A gate is a message that is highlighted with one end connected to the sequence diagram’s frame’s edge and the other end connected to a lifeline.
ReplyDelete