]> git.mxchange.org Git - friendica.git/blob - doc/Developer-Domain-Driven-Design.md
Changes:
[friendica.git] / doc / Developer-Domain-Driven-Design.md
1 Domain-Driven-Design
2 ==============
3
4 * [Home](help)
5   * [Developer Intro](help/Developers-Intro)
6
7 Friendica uses class structures inspired by Domain-Driven-Design programming patterns.
8 This page is meant to explain what it means in practical terms for Friendica development.
9
10 ## Inspiration
11
12 - https://designpatternsphp.readthedocs.io/en/latest/Structural/DependencyInjection/README.html
13 - https://designpatternsphp.readthedocs.io/en/latest/Creational/SimpleFactory/README.html
14 - https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html
15 - https://designpatternsphp.readthedocs.io/en/latest/Creational/FactoryMethod/README.html
16 - https://designpatternsphp.readthedocs.io/en/latest/Creational/Prototype/README.html
17
18 ## Core concepts
19
20 ### Models and Collections
21
22 Instead of anonymous arrays of arrays of database field values, we have Models and collections to take full advantage of PHP type hints.
23
24 Before:
25 ```php
26 function doSomething(array $intros)
27 {
28     foreach ($intros as $intro) {
29         $introId = $intro['id'];
30     }
31 }
32
33 $intros = \Friendica\Database\DBA::selectToArray('intros', [], ['uid' => Session::getLocalUser()]);
34
35 doSomething($intros);
36 ```
37
38 After:
39
40 ```php
41 function doSomething(\Friendica\Contact\Introductions\Collection\Introductions $intros)
42 {
43     foreach ($intros as $intro) {
44         /** @var $intro \Friendica\Contact\Introductions\Entity\Introduction */
45         $introId = $intro->id;
46     }
47 }
48
49 /** @var $intros \Friendica\Contact\Introductions\Collection\Introductions */
50 $intros = \Friendica\DI::intro()->selectForUser(Session::getLocalUser());
51
52 doSomething($intros);
53 ```
54
55 ### Dependency Injection
56
57 Under this concept, we want class objects to carry with them the dependencies they will use.
58 Instead of calling global/static function/methods, objects use their own class members.
59
60 Before:
61 ```php
62 class Model
63 {
64     public $id;
65
66     function save()
67     {
68         return \Friendica\Database\DBA::update('table', get_object_vars($this), ['id' => $this->id]);
69     }
70 }
71 ```
72
73 After:
74 ```php
75 class Model
76 {
77     /**
78      * @var \Friendica\Database\Database
79      */
80     protected $dba;
81
82     public $id;
83
84     function __construct(\Friendica\Database\Database $dba)
85     {
86         $this->dba = $dba;
87     }
88     
89     function save()
90     {
91         return $this->dba->update('table', get_object_vars($this), ['id' => $this->id]);
92     }
93 }
94 ```
95
96 The main advantage is testability.
97 Another one is avoiding dependency circles and avoid implicit initializing.
98 In the first example the method `save()` has to be tested with the `DBA::update()` method, which may or may not have dependencies itself.
99
100 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.
101
102 The main drawback is lengthy constructors for dependency-heavy classes.
103 To alleviate this issue we are using [DiCe](https://r.je/dice) to simplify the instantiation of the higher level objects Friendica uses.
104
105 We also added a convenience factory named `\Friendica\DI` that creates some of the most common objects used in modules.
106
107 ### Factories
108
109 Since we added a bunch of parameters to class constructors, instantiating objects has become cumbersome.
110 To keep it simple, we are using Factories.
111 Factories are classes used to generate other objects, centralizing the dependencies required in their constructor.
112 Factories encapsulate more or less complex creation of objects and create them redundancy free.
113
114 Before:
115 ```php
116 $model = new Model(\Friendica\DI::dba());
117 $model->id = 1;
118 $model->key = 'value';
119
120 $model->save();
121 ```
122
123 After:
124 ```php
125 class Factory
126 {
127     /**
128      * @var \Friendica\Database\Database
129      */
130     protected $dba;
131
132     function __construct(\Friendica\Database\Database $dba)
133     {
134         $this->dba;
135     }
136
137     public function create()
138     {
139         return new Model($this->dba);    
140     }
141 }
142
143 $model = \Friendica\DI::factory()->create();
144 $model->id = 1;
145 $model->key = 'value';
146
147 $model->save();
148 ```
149
150 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.
151
152 ### Repositories
153
154 Last building block of our code architecture, repositories are meant as the interface between models and how they are stored.
155 In Friendica they are stored in a relational database but repositories allow models not to have to care about it.
156 Repositories also act as factories for the Model they are managing.
157
158 Before:
159 ```php
160 class Model
161 {
162     /**
163      * @var \Friendica\Database\Database
164      */
165     protected $dba;
166
167     public $id;
168
169     function __construct(\Friendica\Database\Database $dba)
170     {
171         $this->dba = $dba;
172     }
173     
174     function save()
175     {
176         return $this->dba->update('table', get_object_vars($this), ['id' => $this->id]);
177     }
178 }
179
180 class Factory
181 {
182     /**
183      * @var \Friendica\Database\Database
184      */
185     protected $dba;
186
187     function __construct(\Friendica\Database\Database $dba)
188     {
189         $this->dba;
190     }
191
192     public function create()
193     {
194         return new Model($this->dba);    
195     }
196 }
197
198
199 $model = \Friendica\DI::factory()->create();
200 $model->id = 1;
201 $model->key = 'value';
202
203 $model->save();
204 ```
205
206 After:
207 ```php
208 class Model {
209     public $id;
210 }
211
212 class Repository extends Factory
213 {
214     /**
215      * @var \Friendica\Database\Database
216      */
217     protected $dba;
218
219     function __construct(\Friendica\Database\Database $dba)
220     {
221         $this->dba;
222     }
223
224     public function create()
225     {
226         return new Model($this->dba);    
227     }
228
229     public function save(Model $model)
230     {
231         return $this->dba->update('table', get_object_vars($model), ['id' => $model->id]);
232     }
233 }
234
235 $model = \Friendica\DI::repository()->create();
236 $model->id = 1;
237 $model->key = 'value';
238
239 \Friendica\DI::repository()->save($model);
240 ```