リビジョン | d2a854f870d7453c8aa575e81013b5fa2405be6a (tree) |
---|---|
日時 | 2020-03-22 01:03:32 |
作者 | Albert Mietus < albert AT mietus DOT nl > |
コミッター | Albert Mietus < albert AT mietus DOT nl > |
Pub/Sub: concept done (draft)
@@ -0,0 +1,81 @@ | ||
1 | +.. Copyright (C) 2020: ALbert Mietus. | |
2 | + | |
3 | +.. _PubSub_API: | |
4 | + | |
5 | +=============== | |
6 | +The Pub/Sub API | |
7 | +=============== | |
8 | + | |
9 | +As typical with *Design Patterns* there are many ways to implement it. Here we focus on the “Topic” approach. It | |
10 | +decouples modules by defining a generic interface, and act as a kind of “man-in-the-middle”. | |
11 | + | |
12 | +Topic | |
13 | +===== | |
14 | + | |
15 | +.. currentmodule:: pubsub | |
16 | + | |
17 | +.. class:: Topic | |
18 | + | |
19 | + A :class:`~pubsub.Topic` is like a channel to distribute information (events), in a **Pub**/*Sub* environment. | |
20 | + | |
21 | + This will decouple the :class:`pubsub.AbstractType.Publisher` from the :class:`pubsub.AbstractType.Subscriber` (in | |
22 | + both directions). | |
23 | + | |
24 | + * On one side is a ``Publisher`` that provides ‘data’ (**value**’s, *events*, ...). | |
25 | + * The other side has ``Subscribers`` which subscribe to the topic (with **callbacks**). | |
26 | + * Both ``Publisher`` and ``Subscriber`` are abstract types; there is no contrete class (needed). | |
27 | + * Any module that calls :meth:`publish` is called a ``Publisher``; likewise a ``Subscriber`` is anyone calling | |
28 | + :meth:`subscribe`. | |
29 | + * Commonly there is only one ``Publisher`` (for a given Topic); that is not mandatory however. | |
30 | + | |
31 | + | |
32 | +.. method:: Topic.publish(value, force=False): | |
33 | + | |
34 | + This method is called by the ``Publisher``, whenever new **value** is to be shared. When **force** is `False` | |
35 | + (default), the ``value`` wil only be distributed when it differs from the previous value. To force distribution, set | |
36 | + **force** to `True`. | |
37 | + | |
38 | +.. method:: Topic.subscribe(callback): | |
39 | + | |
40 | + This method is called by all ``Subscribers`` to *register* a **callback**, that is called on new ‘data’. | |
41 | + | |
42 | + .. _PubSub_callback_signature: | |
43 | + | |
44 | + The passed **callback** (any `callable`, like a :func:`~pubsub.AbstractType.callback_function_type` | |
45 | + :meth:`~pubsub.AbstractType.callback_method_type`) will be called when ‘data’ is available. It | |
46 | + has a signature like:: | |
47 | + | |
48 | + def callback([self,] value, topic): | |
49 | + | |
50 | + Where **value** is the ‘data’ passed to :meth:`publish` and **topic** is the :class:`~pubsub.Topic` | |
51 | + instance itself. | |
52 | + | |
53 | + When the callback is a method the `self` parameter is automagical remembered by python. For function-callbacks, leave | |
54 | + it out. | |
55 | + | |
56 | +Supporting types | |
57 | +================ | |
58 | + | |
59 | +.. currentmodule:: pubsub.AbstractType | |
60 | + | |
61 | +Both the ``Publisher`` and the ``Subscribers`` are *Abstract Types*. | |
62 | + | |
63 | +.. class:: Publisher | |
64 | + | |
65 | + Any code that will call :meth:`pubsub.Topic.publish`. It can be a class, a module or just code ... | |
66 | + | |
67 | +.. class:: Subscriber | |
68 | + | |
69 | + Everybody calling :meth:`pubsub.Topic.subscribe`. Typically, the :class:`Subscriber` has a **callback** | |
70 | + function/method too. See :ref:`PubSub_callback_demo` for an example. | |
71 | + | |
72 | + Often, it has a method that act als callback. | |
73 | + | |
74 | + | |
75 | +callbacks | |
76 | +--------- | |
77 | +The generic signature for callbacks is simple: | |
78 | + | |
79 | +.. function:: callback_function_type(value, topic) | |
80 | +.. method:: callback_method_type(self, value, topic) | |
81 | + |
@@ -0,0 +1,42 @@ | ||
1 | +@startuml | |
2 | +hide footbox | |
3 | +participant Publisher as P | |
4 | +participant You as S1 | |
5 | +participant "Somebody" as S2 | |
6 | +participant "Else" as S3 | |
7 | + | |
8 | +== Get your subscription == | |
9 | + | |
10 | +P //-- S1 : Subscribe to newspaper | |
11 | +... | |
12 | +[--\ P : //news// | |
13 | +activate P | |
14 | +P -> S1 : Get your copy | |
15 | +deactivate P | |
16 | + | |
17 | +== Everybody got its own **copy** of the //same// newspaper == | |
18 | + | |
19 | + | |
20 | +P //-- S2 : Another subscription | |
21 | +P //-- S3 : Another subscription | |
22 | + | |
23 | +... | |
24 | +[--\ P : //news// | |
25 | +activate P | |
26 | +P -> S1 | |
27 | +P -> S2 | |
28 | +P -> S3 | |
29 | +deactivate P | |
30 | + | |
31 | +== Until it is canceled == | |
32 | + | |
33 | +P //-- S2 : unsubscribe | |
34 | +... | |
35 | +[--\ P : //news// | |
36 | +activate P | |
37 | +P -> S1 | |
38 | +P -> S3 | |
39 | +deactivate P | |
40 | + | |
41 | + | |
42 | +@enduml |
@@ -0,0 +1,60 @@ | ||
1 | +.. Copyright (C) 2020: ALbert Mietus. | |
2 | + | |
3 | +.. _PubSub_use: | |
4 | + | |
5 | +=== | |
6 | +Use | |
7 | +=== | |
8 | + | |
9 | +As typical with *Design Patterns* there are many ways to implement it. Here we focus on the “Topic” approach. It | |
10 | +decouples modules by defining a generic interface and act as a kind of “man-in-the-middle”. | |
11 | + | |
12 | +Both the ``Publisher`` and the ``Subscribers`` share a common :class:`~pubsub.Topic` instance:: | |
13 | + | |
14 | + from pubsub import Topic | |
15 | + t = Topic("Just a demo") | |
16 | + | |
17 | + | |
18 | +Publishers | |
19 | +========== | |
20 | + | |
21 | +Publishing a value is very simple; assuming ``t`` is a :class:`pubsub.Topic` instance:: | |
22 | + | |
23 | + t.publish("Hello World") | |
24 | + | |
25 | + | |
26 | +Subscribers | |
27 | +=========== | |
28 | + | |
29 | +Each subscriber should register a ``callback``, which will be called “automagical” when a new value is available:: | |
30 | + | |
31 | + t.subscribe(demo_cb) | |
32 | + | |
33 | +Where ``t`` is topic (an instance of :class:`pubsub.Topic`) and ``demo_cb`` is the *callback*. This can a function or other | |
34 | +kind of callable. Multiple subscriptions are possible, by registering another:: | |
35 | + | |
36 | + t.subscribe(demo_oo_cb) | |
37 | + | |
38 | +.. _PubSub_callback_demo: | |
39 | + | |
40 | +callbacks | |
41 | +--------- | |
42 | + | |
43 | +A callback is a callable, that should process the new value. Basically, it is just a function (or method) with the | |
44 | +correct signature. A trivial example is:: | |
45 | + | |
46 | + def demo_cb(value, topic): | |
47 | + print("Function-Demo:: Topic: %s has got the value: %s" %(topic, value)) | |
48 | + | |
49 | +It can also be a method, when you prefer an OO-style:: | |
50 | + | |
51 | + class Demo: | |
52 | + | |
53 | + def demo_oo_cb(self, val, topic): | |
54 | + print("Method-demo: I (%s) got '%s' from topic %s" %(self, val, topic)) | |
55 | + | |
56 | +.. seealso:: | |
57 | + | |
58 | + * :class:`pubsub.AbstractType.Publisher` | |
59 | + * :class:`pubsub.AbstractType.Subscriber` | |
60 | + * :ref:`CallBack Signature <PubSub_callback_signature>` |
@@ -0,0 +1,51 @@ | ||
1 | +.. Copyright (C) 2020: ALbert Mietus. | |
2 | + | |
3 | +Advantages | |
4 | +========== | |
5 | + | |
6 | +For software-engineers, Pub/Sub has many advantages. The most obvious one is *decoupling*, another one is *scaling*. It | |
7 | +is also simple to :ref:`PubSub_use`. | |
8 | + | |
9 | +And, with Pub/Sub you will automatically use | |
10 | +`‘Dependency Inversion’ <https://en.wikipedia.org/wiki/Dependency_inversion_principle>`_, one of the | |
11 | +`SOLID <https://en.wikipedia.org/wiki/SOLID>`_ principles; as well as | |
12 | +`‘Dependency Injection’ <https://en.wikipedia.org/wiki/Dependency_injection>`_, which often simplifies testing. | |
13 | + | |
14 | + | |
15 | +Coupling | |
16 | +-------- | |
17 | + | |
18 | +By and lange, software-routines have to pass information. From one function to another, from one class to another, or | |
19 | +from one module to some other module(s). Especially this latter case is annoying, when it is implemented by calling a | |
20 | +method of that other module. Then, we speak about **tight** (and *static*) **coupling**: the module effectively can’t | |
21 | +perform without the other. When that “other” module is a stable, generic *library*, it is often considered as | |
22 | +acceptable. Although it can disturbed your (unit)-testing; by making it slow. | |
23 | + | |
24 | +But how about two modules, that are under construction? | |
25 | +|BR| | |
26 | +Then, both are not “stable” (as they might develop) and being depended on unstable modules is bad. You can’t test | |
27 | +independently, you may need to revise when the other is updated, etc. Well you know the troubles ... | |
28 | + | |
29 | +To overcome this, the modules should be *uncoupled* or *lousily coupled*: Both modules are not allowed to call a | |
30 | +function/method of the other one. (Which is easy:-). But still pass information; which might seen impossible at first. | |
31 | + | |
32 | +This is possible, as the modules do not dependent on each other; instead they both depend on the generic | |
33 | +:class:`pubsub.Topic`, as we can see on the :ref:`next page<PubSub_use>` | |
34 | + | |
35 | + | |
36 | +Scaling | |
37 | +------- | |
38 | + | |
39 | +Now and then the same data is needed by multiple “consumers”. That number of “users” may even grow in future releases. A | |
40 | +sensor-value, by example, that was initially only used in one or two routines, may becomes relevant input to many new, | |
41 | +fancy features. | |
42 | + | |
43 | +Imagene a modules that handles (pushing) the brake. Initially it was only needed to slow down the car. Nowadays it will | |
44 | +switch-off the cruise control, also. Perhaps, in the future, that same data might influence the volume of the radio; or is | |
45 | +needed to automatically “e-call” 112, when there is an serious road accident. Or ... | |
46 | + | |
47 | +With Pub/Sub, it is easy to distribute data to more and more modules. Even to modules that aren’t yet imagined when you | |
48 | +write that sensor-routine! Your current module only has use :meth:`pubsub.Topic.publish`, and that future module can | |
49 | +get that data using :meth:`pubsub.Topic.subscribe`; easy! | |
50 | + | |
51 | + |
@@ -0,0 +1,25 @@ | ||
1 | +.. Copyright (C) 2020: ALbert Mietus. | |
2 | + | |
3 | +Introduction | |
4 | +============ | |
5 | + | |
6 | +An almost trivial example of PubSub is the daily newspaper: | |
7 | + | |
8 | + Once you get an subscription, you automatically get each new publication. | |
9 | + | |
10 | +Typically, your are not the only one. The *same* ‘newspaper’ is send to all subscribers: everybody got his own | |
11 | +*copy*. And, when somebody cancels his subscription all others still het there daily update. | |
12 | +|BR| | |
13 | +Also notice: your copy is undependent of that the neighbours. And, until you subscribe, the publisher does not know | |
14 | +you. | |
15 | + | |
16 | +In software-engineering we call that **uncoupled** or *loosely coupled*; and that is great. | |
17 | + | |
18 | + | |
19 | +.. uml:: PubSub-newspaper.puml | |
20 | + | |
21 | +.. toctree:: | |
22 | + | |
23 | + advantages | |
24 | + Use | |
25 | + API |
@@ -8,12 +8,12 @@ | ||
8 | 8 | |
9 | 9 | .. sidebar:: **HighTech** vs *Gooogling* |
10 | 10 | |
11 | - When you search for `Pub/Sub`, you will find over 312M hits! Mostly about cloud-computing, and on how servers exchange | |
12 | - messages via a *‘broker’*. | |
11 | + When you search for `Pub/Sub`, you will find over 312M hits! Most are about cloud-computing and on how to exchange messages | |
12 | + between servers via a *‘broker’*. | |
13 | 13 | |BR| |
14 | - Often it is infrastructure engenering based. | |
14 | + Often it is infrastructure engineering based. | |
15 | 15 | |
16 | - This workshop isn’t about that. We focus on *Modern, Embedded Software Systems*: How can an (HighTech) Software Engineer use | |
16 | + This workshop isn’t about that. We focus on *Modern, Embedded Software Systems*: How can a (HighTech) Software Engineer use | |
17 | 17 | this pattern to create better software. |
18 | 18 | |
19 | 19 |
@@ -59,4 +59,4 @@ | ||
59 | 59 | |
60 | 60 | .. seealso:: |
61 | 61 | |
62 | - * ... | |
62 | + * https://mybinder.org/v2/gh/AlbertMietus/PyMess.ipython-notebools/master?filepath=%2FSoftwareCompetence%2FDesignWorkShops%2FPubSub%2FPubSub-demo.ipynb |
@@ -0,0 +1,33 @@ | ||
1 | +/* SwBMnl-slides style for Juyiter/IPython "download as slides" ... | |
2 | + The custom.css file should import it as: | |
3 | + @import url("/_static/SwBMnl-slides.css"); | |
4 | +*/ | |
5 | + | |
6 | +/* Gray slides on gray background with blue border*/ | |
7 | +.reveal .slide-background { background: #808080;} | |
8 | +.reveal div.slides { | |
9 | + background: #c0c0c0; | |
10 | + border: 2px solid #000066; | |
11 | +} | |
12 | + | |
13 | +/* Also gray slides in overview-mode. Highligh the current slide a bit */ | |
14 | +.reveal.overview .slides section { background: #808080;} | |
15 | +.reveal.overview .slides section.present { background: #c0c0c0; border: 2px solid #000066;} | |
16 | + | |
17 | +/* H1, H2 headers in blue; top aligned. H1 in gray backgroud. In contact with (gray, see bellow side)*/ | |
18 | +.reveal .slides h1 { | |
19 | + color: #000066; background: #808080; | |
20 | + margin-left: -0.5em; padding-left: 1em; | |
21 | + margin-top: -0.5em; padding-top: 0.5em; padding-bottom: 0.5em; | |
22 | +} | |
23 | +.reveal .slides h2 { | |
24 | + color: #000066; | |
25 | + margin-top: -0.5em; padding-top: 0.5em; | |
26 | +} | |
27 | + | |
28 | +/* Slighly style the input "sidebar". Make the "Header" text-cell-promt gray; as sidebar, in contact with H1 (see above) */ | |
29 | +.reveal .prompt.input_prompt { border: 1px solid #808080; } | |
30 | +.reveal div.text_cell .prompt.input_prompt { border: 1px solid #808080; background:#808080;} | |
31 | + | |
32 | +/* code section ...*/ | |
33 | +.reveal .slides section code {background: #808080; color: #000066;} |
@@ -54,4 +54,3 @@ | ||
54 | 54 | '**': [ 'recentposts.html', 'tagcloud.html', 'postcardHeader.html'], |
55 | 55 | } |
56 | 56 | |
57 | -html_static_path.append('_static') |