-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Service-oriented design notebook
- Loading branch information
Showing
8 changed files
with
9,492 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
375 changes: 375 additions & 0 deletions
375
notebooks/.ipynb_checkpoints/service-oriented-design-checkpoint.ipynb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,375 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "markdown", | ||
"id": "f80155d4-102c-4d6e-af61-bc22b4bf4b72", | ||
"metadata": {}, | ||
"source": [ | ||
"# Notes on the \"Service-oriented design\" OOP domain modelling example\n", | ||
"\n", | ||
"This notebook contains notes from studying the [Service Oriented Design](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#advanced-example-service-oriented-design) example in the Scala Book, part of the official Scala 3 documentation.\n", | ||
"\n", | ||
"The example is in turn based on the paper by Martin Odersky and Matthias Zenger, [\"Scalable Component Abstractions\"](https://doi.org/10.1145/1094811.1094815).\n", | ||
"\n", | ||
"> Our goal is to define a software component with a _family of types_ that can be refined later in implementations of the component. Concretely, the following code defines the component `SubjectObserver` as a trait with two abstract type members, `S` (for subjects) and `O` (for observers)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "00b31d02-3aaa-45c4-86f4-7aa0720c4bd5", | ||
"metadata": {}, | ||
"source": [ | ||
"## The `SubjectObserver` trait" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 1, | ||
"id": "04b5468d-6c11-4d2b-8824-6a0210c2ca38", | ||
"metadata": {}, | ||
"outputs": [ | ||
{ | ||
"data": { | ||
"text/plain": [ | ||
"defined \u001b[32mtrait\u001b[39m \u001b[36mSubjectObserver\u001b[39m" | ||
] | ||
}, | ||
"execution_count": 1, | ||
"metadata": {}, | ||
"output_type": "execute_result" | ||
} | ||
], | ||
"source": [ | ||
"trait SubjectObserver:\n", | ||
" type S <: Subject\n", | ||
" type O <: Observer\n", | ||
"\n", | ||
" trait Subject:\n", | ||
" self: S =>\n", | ||
" private var observers: List[O] = List()\n", | ||
" def subscribe(obs: O): Unit =\n", | ||
" observers = obs :: observers\n", | ||
" def publish() =\n", | ||
" for obs <- observers do obs.notify(this)\n", | ||
"\n", | ||
" trait Observer:\n", | ||
" def notify(sub: S): Unit" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "1cc48aa5-4b2d-4b44-95d1-0eef09cc48e7", | ||
"metadata": {}, | ||
"source": [ | ||
"### Top-level definitions" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "b4a412a5-4252-4e57-9035-90b33581141d", | ||
"metadata": {}, | ||
"source": [ | ||
"In the `SubjectObserver` trait's top level, four members are defined:\n", | ||
"\n", | ||
"Two abstract types:\n", | ||
"- `S` (implying subject)\n", | ||
"- `O` (implying observer)\n", | ||
"\n", | ||
"Two other traits:\n", | ||
"- `Subject`\n", | ||
"- `Observer`" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "3e1e377f-941d-436b-a8d7-9e28683b3efa", | ||
"metadata": {}, | ||
"source": [ | ||
"### Type definitions" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "59db0e6d-1013-4751-a81e-2cd204af5b22", | ||
"metadata": {}, | ||
"source": [ | ||
"The syntax `type S <: Subject`, called an \"**upper bound**\", is a declaration for an abstract type `S` which must be a subtype of `Subject`.\n", | ||
"\n", | ||
"Meaning that:\n", | ||
"> All traits and classes extending `SubjectObserver` are free to choose any type for `S` as long as the chosen type is a subtype of `Subject`.\n", | ||
"\n", | ||
"Similarly, a type `O` is also defined, which must be a subtype of `Observer`." | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "a03d3dbd-e675-45af-bda2-297b634bed19", | ||
"metadata": {}, | ||
"source": [ | ||
"### Trait definitions" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "fc0f38b8-14af-4cfe-a47b-e27344e52a4c", | ||
"metadata": {}, | ||
"source": [ | ||
"Each minor trait has the following members:\n", | ||
"\n", | ||
"- **Subject**\n", | ||
" - `private var observers: List[0] = List()`\n", | ||
" - `def subscribe(obs: O: Unit = ...`\n", | ||
" - `def publish() = ...`\n", | ||
"- **Observer**\n", | ||
" - `def notify(sub: S): Unit`\n", | ||
"\n", | ||
"All of the members of the `Subject` trait are concrete, while the single member of the `Observer` trait is abstract." | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "7fc1cea9-09c3-4f08-a014-f03c6ba70c51", | ||
"metadata": {}, | ||
"source": [ | ||
"#### Subject" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "786b27b8-e217-430f-bfed-e0ad227b2109", | ||
"metadata": {}, | ||
"source": [ | ||
"The `Subject` trait defines three concrete members:\n", | ||
"- A private, mutable field `observers` of type `List[O]`, which is initialized with `List()`\n", | ||
"- Two public, concrete methods whose behavior depend on this field:\n", | ||
" - `subscribe(obs: O): Unit`\n", | ||
" - `publish(): Unit`\n", | ||
"\n", | ||
"The `subscribe` method appears to simply add a given `O` observer to the list of subscribers set in the `observers` field.\n", | ||
"\n", | ||
"The `publish` method in turn seems to enable a `Subject` to go over each of its subscribers, set in the `observers` field, and call their `notify` method with the subject itself as the argument." | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "a28c62aa-c39a-430d-989c-91941d331b81", | ||
"metadata": {}, | ||
"source": [ | ||
"Something that stands out is that, unlike for `Observer`, the `Subject` trait has `self: S =>` before its member definitions. This is also [explained](https://docs.scala-lang.org/scala3/book/domain-modeling-oop.html#self-type-annotations) in the documentation:\n", | ||
"\n", | ||
"> This is called a _self-type annotation_. It requires subtypes of `Subject` to also be subtypes of `S`. This is necessary to be able to call `obs.notify` with `this` as an argument, since it requires a value of type `S`. If `S` was a _concrete_ type, the self-type annotation could be replaced by `trait Subject extends S`." | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "cf4deab8-d89c-4729-a2e2-2c2eaf1d335c", | ||
"metadata": {}, | ||
"source": [ | ||
"#### Observer" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "e3e028d1-1913-472d-96b8-44560239dd51", | ||
"metadata": {}, | ||
"source": [ | ||
"The `Observer` trait defines a single, abstract method named `notify`, which takes an argument of type `S` named `sub`.\n", | ||
"\n", | ||
"My understanding at this point is that it's meant to be called by a subject when it needs to notify the subscribed observer of something." | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "1ccb2747-02b3-4617-8c16-5fa6fce51e8d", | ||
"metadata": {}, | ||
"source": [ | ||
"## A `SubjectObserver` implementation" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 2, | ||
"id": "a11f16d2-ab41-4351-9613-926bb4f5dbb9", | ||
"metadata": {}, | ||
"outputs": [ | ||
{ | ||
"data": { | ||
"text/plain": [ | ||
"defined \u001b[32mobject\u001b[39m \u001b[36mSensorReader\u001b[39m" | ||
] | ||
}, | ||
"execution_count": 2, | ||
"metadata": {}, | ||
"output_type": "execute_result" | ||
} | ||
], | ||
"source": [ | ||
"object SensorReader extends SubjectObserver:\n", | ||
" type S = Sensor\n", | ||
" type O = Display\n", | ||
"\n", | ||
" class Sensor(val label: String) extends Subject:\n", | ||
" private var currentValue = 0.0\n", | ||
" def value = currentValue\n", | ||
" def changeValue(v: Double) =\n", | ||
" currentValue = v\n", | ||
" publish()\n", | ||
"\n", | ||
" class Display extends Observer:\n", | ||
" def notify(sub: Sensor) =\n", | ||
" println(s\"${sub.label} has value ${sub.value}\")" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "96c9767d-9dcb-4a7d-9009-cc32cb319c2e", | ||
"metadata": {}, | ||
"source": [ | ||
"Here, a `SensorObject` singleton is defined extending the previously defined `SubjectObserver` trait.\n", | ||
"\n", | ||
"To satisfy its contract, it defines:\n", | ||
"- A type `S`, referring to the trait's \"subject\" concept, which is assigned the class `Sensor`, this class defined in the body of the object\n", | ||
"- A type `O`, referring to the trait's \"observer\" concept, which is assigned to the class `Display`, also defined in the body of the object\n", | ||
"\n", | ||
"It still must implement the minor traits `Subject` and `Observer` to fulfill the contract, and it does that in defining the previously assigned types:\n", | ||
"\n", | ||
"- The class `Sensor` takes a string `label` value for its constructor and extends `Subject`\n", | ||
"- The class `Display` takes no arguments for its constructor and extends `Observer`\n", | ||
"\n", | ||
"The `Sensor` class does not need to implement any methods since the `Subject` trait has all concrete methods.\n", | ||
"\n", | ||
"However, it _does_ have three members that provide a way to access and modify a `currentValue` private var, initialized to the double 0.0. This encapsulated logic ensures that whenever the `currentValue` is modified, `publish` is also called, therefore notifying all subscribed observers.\n", | ||
"\n", | ||
"The `Display` class however must implement a `notify(sub: S)` method that handles a notification event.\n", | ||
"\n", | ||
"It does so by printing to STDOUT the interpolated string `\"${sub.label} has value ${sub.value}\"`\n", | ||
"\n", | ||
"The interpolated string relies on members of the `Sensor` class defined in the implementation alone, rather than inherited from the trait `Subject` trait." | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "688f3a5b-510c-48a2-8b05-8273a3d220ef", | ||
"metadata": {}, | ||
"source": [ | ||
"The documentation emphasizes how this design demonstrates an object-oriented paradigm:\n", | ||
"\n", | ||
"> Besides, being an example of a service oriented design, this code also highlights many aspects of object-oriented programming:\n", | ||
"\n", | ||
"> - The class `Sensor` introduces its own private state (`currentValue`) and encapsulates modification of the state behind the method `changeValue`.\n", | ||
"> - The implementation of `changeValue` uses the method `publish` defined in the extended trait.\n", | ||
"> - The class `Display` extends the trait `Observer`, and implements the missing method `notify`." | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "2e11c23a-c3ec-4934-80fc-9eaacca745ab", | ||
"metadata": {}, | ||
"source": [ | ||
"It also makes an interesting observation:\n", | ||
"\n", | ||
"> It is important to point out that the implementation of `notify` can only safely access the label and value of `sub`, since we originally declared the parameter to be of type `S`.\n", | ||
"\n", | ||
"It is alluding to the contract established in the nested `Observer` trait:\n", | ||
"\n", | ||
"```scala\n", | ||
"trait SubjectObserver:\n", | ||
" type S <: Subject\n", | ||
" // ...\n", | ||
"\n", | ||
" trait Subject:\n", | ||
" self: S =>\n", | ||
" // ...\n", | ||
"\n", | ||
"trait Observer:\n", | ||
" def notify(sub: S): Unit\n", | ||
"```\n", | ||
"\n", | ||
"Because `S` " | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "c8afa140-88a1-450c-885a-af355f3ee3dc", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "990c0e8a-6495-4561-8833-8e426d7af7e7", | ||
"metadata": {}, | ||
"source": [ | ||
"## Invoking the `SensorReader` logic" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 4, | ||
"id": "a62d18c3-cc2e-4368-b1a4-159e3c464ec7", | ||
"metadata": {}, | ||
"outputs": [ | ||
{ | ||
"name": "stdout", | ||
"output_type": "stream", | ||
"text": [ | ||
"sensor1 has value 2.0\n", | ||
"sensor1 has value 2.0\n", | ||
"sensor2 has value 3.0\n" | ||
] | ||
}, | ||
{ | ||
"data": { | ||
"text/plain": [ | ||
"\u001b[32mimport \u001b[39m\u001b[36mSensorReader.*\n", | ||
"\n", | ||
"// setting up a network\n", | ||
"\u001b[39m\n", | ||
"\u001b[36ms1\u001b[39m: \u001b[32mSensor\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@dcc17ef\n", | ||
"\u001b[36ms2\u001b[39m: \u001b[32mSensor\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Sensor@1b7edae8\n", | ||
"\u001b[36md1\u001b[39m: \u001b[32mDisplay\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@7ea3d970\n", | ||
"\u001b[36md2\u001b[39m: \u001b[32mDisplay\u001b[39m = ammonite.$sess.cell2$Helper$SensorReader$Display@4c7c8b68" | ||
] | ||
}, | ||
"execution_count": 4, | ||
"metadata": {}, | ||
"output_type": "execute_result" | ||
} | ||
], | ||
"source": [ | ||
"import SensorReader.*\n", | ||
"\n", | ||
"// setting up a network\n", | ||
"val s1 = Sensor(\"sensor1\")\n", | ||
"val s2 = Sensor(\"sensor2\")\n", | ||
"val d1 = SensorReader.Display()\n", | ||
"val d2 = SensorReader.Display()\n", | ||
"s1.subscribe(d1)\n", | ||
"s1.subscribe(d2)\n", | ||
"s2.subscribe(d1)\n", | ||
"\n", | ||
"// propagating updates through the network\n", | ||
"s1.changeValue(2)\n", | ||
"s2.changeValue(3)" | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"kernelspec": { | ||
"display_name": "Scala", | ||
"language": "scala", | ||
"name": "scala" | ||
}, | ||
"language_info": { | ||
"codemirror_mode": "text/x-scala", | ||
"file_extension": ".sc", | ||
"mimetype": "text/x-scala", | ||
"name": "scala", | ||
"nbconvert_exporter": "script", | ||
"version": "2.13.12" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 5 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
|
||
# scala-notes notebooks | ||
|
||
These Jupyter Notebooks go a bit more in depth on some of the explored content: | ||
|
||
- [Service-oriented design](service-oriented-design) | ||
- [Markdown](service-oriented-design/service-oriented-design.md) | ||
- [HTML](service-oriented-design/service-oriented-design.html) | ||
- [PDF](service-oriented-design/service-oriented-design.pdf) |
Oops, something went wrong.