From 0270b6adb94444260aec1254c2a17a19099cf78f Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Wed, 15 Jan 2025 13:23:52 +0100 Subject: [PATCH 1/2] [TASK] Improve "Services" chapter --- .../PhpArchitecture/Services.rst | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/Documentation/CodingGuidelines/PhpArchitecture/Services.rst b/Documentation/CodingGuidelines/PhpArchitecture/Services.rst index fa263ec148..d62852b57d 100644 --- a/Documentation/CodingGuidelines/PhpArchitecture/Services.rst +++ b/Documentation/CodingGuidelines/PhpArchitecture/Services.rst @@ -11,72 +11,72 @@ Characteristics =============== * Services MUST be used as objects, they are never static. - -* A single service MUST consist of one class only. - -* Services MUST be located in a :file:`Service/` directory and the class and file name MUST end - with :code:`Service`, eg. :file:`Service/FoobarService.php`. - -* Service instances MAY hold a state, but they SHOULD be stateless. - +* Services SHOULD be stateless and shared. * Services MAY use their own configuration, but they SHOULD not. - -* Services MAY have multiple entry points, but they SHOULD have only one. - -* Services SHOULD NOT be singletons +* Services MAY have dependencies to other services and SHOULD get them + injected using TYPO3 core dependency injection. Rationale ========= -A “service” in this context is meant as the relatively short-sighted -process of putting a class into a :file:`Service/` subfolder and calling -it a :code:`WhateverService`. It does not have too much to do with the -DDD Service context, which is broader. This section is just about which -scope can be expected for classes residing in a Service folder within -Core extensions. - -From this point of view, a service in the TYPO3 world is a relatively slim -class construct that encapsulates a specific concern. It is too big for -a small static method, it may hold a state, but it is still just a -relatively small scope. Each service consists typically of only a single -class. A bigger construct with interfaces, multiple sub classes is not -called a service anymore. - -The above characteristica MAY and SHOULD mean that a single service MAY do a single one -or two of them, but if for instance a service would become relatively big, -if it would have many entry points, if it would keep states and depend on configuration, -this would be too much. This would be a sign that it should be modeled in a different and more -dedicated and more disjoint way. - -The main risk with service classes is that they pile up to a -conglomeration of helper stuff classes that are hanging around without -good motivation. It is important that a service class should not be a -bin for something that just does not fit to a different better place -within the scope of a specific extension. - +Modern PHP programming primarily involves two types of classes: Services and +data objects (DO). + +This distinction has gained significance with the introduction of +:ref:`dependency injection ` in the TYPO3 core. + +A well-designed service class comprise of one or more methods that process data. +These methods may not return any value, acting as a data sink. For example, a +mailer service might take a mail data object to send it. Conversely, service +methods often return new or modified data based on input. A typical example is a +repository service that accepts an identifier (e.g. the uid of a product) and returns +a data object (the product). + +Services may depend on other services and should use dependency injection to obtain +these dependencies, typically via :ref:`constructor injection `. + +In TYPO3, most classes are service classes unless they function as data objects to +transport data. + +Services should be stateless. The result of a service method call should only depend +on the given input arguments and the service should not keep track of previous calls +made. This is an important aspect of well crafted services. It reduces complexity +significantly when a service does not hold data itself: It does not matter +how often or in which context that service may have been called before. It also means +that stateless services can be "shared" and declared :php:`readonly`: They are instantiated +only once and the same instance is injected to all dependent services. + +TYPO3 core has historically stateful services that blend service class and data object +characteristics. These stateful services pose issues in a service-oriented architecture: +Injecting a stateful service into a stateless service make the latter stateful, potentially +causing unpredictable behavior based on the prior state of the injected service. A clear +example is the core :php:`DataHandler` class which modifies numerous data properties when +its primary API methods are called. Such instances become "tainted" after use, and should +not be injected but created on-demand using :php:`GeneralUtility::makeInstance()`. Good Examples ============= -* :php:`\TYPO3\CMS\Extbase\Service\CacheService` +* :php:`\TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools` has been refactored in + TYPO3 v13 to be stateless: - * Small and straight scope with useful helpers - - * It is a singleton, but that is feasible in this case + * A shared and readonly service with a few dependencies + * A clear scope with reasonable API methods + * No data properties Bad Examples ============ -* :php:`\TYPO3\CMS\Core\Service\AbstractAuthenticationService`, - - * Not modeled in a sane way, this should be within :file:`Core/Authentication` +* :php:`\TYPO3\CMS\Core\DataHandling\DataHandler` - * Far too complex, class abstraction and extending classes + * Far too complex + * Heavily stateful Further Reading =============== -See http://gorodinski.com/blog/2012/04/14/services-in-domain-driven-design-ddd/. +* http://gorodinski.com/blog/2012/04/14/services-in-domain-driven-design-ddd/ +* https://igor.io/2013/03/31/stateless-services.html From abd00d04156cfe0de2f26b5f701c7e3f77c71db6 Mon Sep 17 00:00:00 2001 From: Lina Wolf <48202465+linawolf@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:29:24 +0100 Subject: [PATCH 2/2] Update Documentation/CodingGuidelines/PhpArchitecture/Services.rst --- Documentation/CodingGuidelines/PhpArchitecture/Services.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/CodingGuidelines/PhpArchitecture/Services.rst b/Documentation/CodingGuidelines/PhpArchitecture/Services.rst index d62852b57d..e4c89bad73 100644 --- a/Documentation/CodingGuidelines/PhpArchitecture/Services.rst +++ b/Documentation/CodingGuidelines/PhpArchitecture/Services.rst @@ -14,7 +14,7 @@ Characteristics * Services SHOULD be stateless and shared. * Services MAY use their own configuration, but they SHOULD not. * Services MAY have dependencies to other services and SHOULD get them - injected using TYPO3 core dependency injection. + injected using TYPO3 Core dependency injection. Rationale