]> git.mxchange.org Git - friendica.git/commitdiff
Create documentation about Domain-Driven-Design
authorHypolite Petovan <hypolite@mrpetovan.com>
Sun, 5 Jan 2020 22:55:16 +0000 (17:55 -0500)
committerHypolite Petovan <hypolite@mrpetovan.com>
Tue, 7 Jan 2020 02:39:05 +0000 (21:39 -0500)
doc/Developer-Domain-Driven-Design.md [new file with mode: 0644]
doc/Developers-Intro.md
doc/Home.md

diff --git a/doc/Developer-Domain-Driven-Design.md b/doc/Developer-Domain-Driven-Design.md
new file mode 100644 (file)
index 0000000..b8d886a
--- /dev/null
@@ -0,0 +1,239 @@
+Domain-Driven-Design
+==============
+
+* [Home](help)
+  * [Developer Intro](help/Developers-Intro)
+
+Friendica uses class structures inspired by Domain-Driven-Design programming patterns.
+This page is meant to explain what it means in practical terms for Friendica development.
+
+## Inspiration
+
+- https://designpatternsphp.readthedocs.io/en/latest/Structural/DependencyInjection/README.html
+- https://designpatternsphp.readthedocs.io/en/latest/Creational/SimpleFactory/README.html
+- https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html
+- https://designpatternsphp.readthedocs.io/en/latest/Creational/FactoryMethod/README.html
+- https://designpatternsphp.readthedocs.io/en/latest/Creational/Prototype/README.html
+
+## Core concepts
+
+### Models and Collections
+
+Instead of anonymous arrays of arrays of database field values, we have Models and collections to take full advantage of PHP type hints.
+
+Before:
+```php
+function doSomething(array $intros)
+{
+    foreach ($intros as $intro) {
+        $introId = $intro['id'];
+    }
+}
+
+$intros = \Friendica\Database\DBA::selectToArray('intros', [], ['uid' => local_user()]);
+
+doSomething($intros);
+```
+
+After:
+```php
+function doSomething(\Friendica\Collection\Introductions $intros)
+{
+    foreach ($intros as $intro) {
+        /** @var $intro \Friendica\Model\Introduction */
+        $introId = $intro->id;
+    }
+}
+
+/** @var $intros \Friendica\Collection\Introductions */
+$intros = \Friendica\DI::intro()->select(['uid' => local_user()]);
+
+doSomething($intros);
+```
+
+### Dependency Injection
+
+Under this concept, we want class objects to carry with them the dependencies they will use.
+Instead of calling global/static function/methods, objects use their own class members.
+
+Before:
+```php
+class Model
+{
+    public $id;
+
+    function save()
+    {
+        return \Friendica\Database\DBA::update('table', get_object_vars($this), ['id' => $this->id]);
+    }
+}
+```
+
+After:
+```php
+class Model
+{
+    /**
+     * @var \Friendica\Database\Database
+     */
+    protected $dba;
+
+    public $id;
+
+    function __construct(\Friendica\Database\Database $dba)
+    {
+        $this->dba = $dba;
+    }
+    
+    function save()
+    {
+        return $this->dba->update('table', get_object_vars($this), ['id' => $this->id]);
+    }
+}
+```
+
+The main advantage is testability.
+Another one is avoiding dependency circles and avoid implicit initializing.
+In the first example the method `save()` has to be tested with the `DBA::update()` method, which may or may not have dependencies itself.
+
+In the second example we can mock `\Friendica\Database\Database`, e.g. overload the class by replacing its methods by placeholders, which allows us to test only `Model::save()` and nothing else implicitly.
+
+The main drawback is lengthy constructors for dependency-heavy classes.
+To alleviate this issue we are using [DiCe](https://r.je/dice) to simplify the instantiation of the higher level objects Friendica uses.
+
+We also added a convenience factory named `\Friendica\DI` that creates some of the most common objects used in modules.
+
+### Factories
+
+Since we added a bunch of parameters to class constructors, instantiating objects has become cumbersome.
+To keep it simple, we are using Factories.
+Factories are classes used to generate other objects, centralizing the dependencies required in their constructor.
+Factories encapsulate more or less complex creation of objects and create them redundancy free.
+
+Before:
+```php
+$model = new Model(\Friendica\DI::dba());
+$model->id = 1;
+$model->key = 'value';
+
+$model->save();
+```
+
+After:
+```php
+class Factory
+{
+    /**
+     * @var \Friendica\Database\Database
+     */
+    protected $dba;
+
+    function __construct(\Friendica\Database\Database $dba)
+    {
+        $this->dba;
+    }
+
+    public function create()
+    {
+        return new Model($this->dba);    
+    }
+}
+
+$model = \Friendica\DI::factory()->create();
+$model->id = 1;
+$model->key = 'value';
+
+$model->save();
+```
+
+Here, `DI::factory()` returns an instance of `Factory` that can then be used to create a `Model` object without having to care about its dependencies.
+
+### Repositories
+
+Last building block of our code architecture, repositories are meant as the interface between models and how they are stored.
+In Friendica they are stored in a relational database but repositories allow models not to have to care about it.
+Repositories also act as factories for the Model they are managing.
+
+Before:
+```php
+class Model
+{
+    /**
+     * @var \Friendica\Database\Database
+     */
+    protected $dba;
+
+    public $id;
+
+    function __construct(\Friendica\Database\Database $dba)
+    {
+        $this->dba = $dba;
+    }
+    
+    function save()
+    {
+        return $this->dba->update('table', get_object_vars($this), ['id' => $this->id]);
+    }
+}
+
+class Factory
+{
+    /**
+     * @var \Friendica\Database\Database
+     */
+    protected $dba;
+
+    function __construct(\Friendica\Database\Database $dba)
+    {
+        $this->dba;
+    }
+
+    public function create()
+    {
+        return new Model($this->dba);    
+    }
+}
+
+
+$model = \Friendica\DI::factory()->create();
+$model->id = 1;
+$model->key = 'value';
+
+$model->save();
+```
+
+After:
+```php
+class Model {
+    public $id;
+}
+
+class Repository extends Factory
+{
+    /**
+     * @var \Friendica\Database\Database
+     */
+    protected $dba;
+
+    function __construct(\Friendica\Database\Database $dba)
+    {
+        $this->dba;
+    }
+
+    public function create()
+    {
+        return new Model($this->dba);    
+    }
+
+    public function save(Model $model)
+    {
+        return $this->dba->update('table', get_object_vars($model), ['id' => $model->id]);
+    }
+}
+
+$model = \Friendica\DI::repository()->create();
+$model->id = 1;
+$model->key = 'value';
+
+\Friendica\DI::repository()->save($model);
+```
index ae9a856b8e784025ba5e337487f7200b80148a6e..f72ff4abeb0189a04e26a17767dd8259b768322c 100644 (file)
@@ -41,6 +41,8 @@ If you have seen Friendica you probably have ideas to improve it, haven't you?
 
 ## Programming
 
+Friendica uses an implementation of [Domain-Driven-Design](help/Developer-Domain-Driven-Design), please make sure to check out the provided links for hints at the expected code architecture.
+
 ### Composer
 
 Friendica uses [Composer](https://getcomposer.org) to manage dependencies libraries and the class autoloader both for libraries and namespaced Friendica classes.
index d58cfeca6a2a28bffb7eed5151589d465a00a5f5..9ed552bd3a8a6a3be884aa3831b89de9cf92c000 100644 (file)
@@ -45,6 +45,7 @@ Friendica Documentation and Resources
        * [Help on Vagrant](help/Vagrant)
        * [Bugs and Issues](help/Bugs-and-Issues)
 * Code structure
+    * [Domain-Driven-Design](help/Developer-Domain-Driven-Design)
        * [Addon Development](help/Addons)
        * [Theme Development](help/themes)
        * [Smarty 3 Templates](help/smarty3-templates)