{"id":434,"date":"2023-07-30T17:39:31","date_gmt":"2023-07-30T23:39:31","guid":{"rendered":"https:\/\/horazmakes.com\/blog\/?p=434"},"modified":"2023-07-30T17:42:28","modified_gmt":"2023-07-30T23:42:28","slug":"spring-boot-decoupled-service-factory","status":"publish","type":"post","link":"https:\/\/horazmakes.com\/blog\/2023\/07\/30\/spring-boot-decoupled-service-factory\/","title":{"rendered":"Spring Boot decoupled Service Factory"},"content":{"rendered":"\n<p><strong>Introduction<\/strong><\/p>\n\n\n\n<p>Below we have a nice decoupled Service Factory example in Java, using Spring Boot. What is very cool about below approach is that you can add as many service implementations as required by declaring a new Spring component that implements your Service interface. No need to update the Service Factory itself.<\/p>\n\n\n\n<p><strong>Service Interface<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.horazmakes.servicefactorytest;\n\npublic interface Service {\n\n\tpublic void process();\n\tpublic String getName();\n\t\n}<\/pre>\n\n\n\n<p>This is the contract all of our future service implementations will have to honor. The process() method will do the main task in each service. On the other hand, by calling the method getName() we will be able to identify every service.<\/p>\n\n\n\n<p><strong>ServiceFactory<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.horazmakes.servicefactorytest;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport javax.annotation.PostConstruct;\n\nimport org.springframework.stereotype.Component;\n\nimport lombok.extern.slf4j.Slf4j;\n\n@Slf4j\n@Component\npublic class ServiceFactory {\n\n\tprivate List&lt;Service> services;\n\t\n\tprivate static final Map&lt;String, Service> serviceMap = new HashMap&lt;>();\n\t\n\tpublic ServiceFactory(List&lt;Service> services) {\n\t\tthis.services = services;\n\t}\n\t\n\t@PostConstruct\n\tprivate void initServiceFactory() {\n\t\tlog.info(\"Initializing ServiceFactory...\");\n\t\tfor (Service service : services) {\n\t\t\tlog.info(\"Adding {}...\", service.getName());\n\t\t\tserviceMap.put(service.getName(), service);\n\t\t}\n\t}\n\t\n\tpublic Service getServiceByName(String name) {\n\t\tService service = serviceMap.get(name);\n\t\t\n\t\tif (service == null) {\n\t\t\tthrow new RuntimeException(\"Unknown service \" + name);\n\t\t}\n\t\t\n\t\treturn service;\n\t}\n\t\n}<\/pre>\n\n\n\n<p>The ServiceFactory will be annotated with @Component so that we can use it as a Spring bean later and also it participates in dependency injection mechanism during application startup, initializing the factory with all available services.<\/p>\n\n\n\n<p>The ServiceFactory has a services list. Spring will automatically instantiate each service (@Component) and inject all of them to ServiceFactory through constructor injection. All that Spring takes to add your service to the ServiceFactory is that your Service implements Service implementation above.<\/p>\n\n\n\n<p>The method initServiceFactory() will be called just once, after the ServiceFactory object has been created (it is annotated with @PostConstruct). The initialization method will populate the <em>serviceMap<\/em> that will hold the names of all the found services and reference to all of them.<\/p>\n\n\n\n<p>Finally, by using the method getServiceByName(String name) we can dynamically fetch our different services at runtime. Since the objects will be instantiated during application startup, the method will return the reference to the object right away if the passed name is good, otherwise it will throw an exception.<\/p>\n\n\n\n<p><strong>Service implementation<\/strong><\/p>\n\n\n\n<p>Now we can declare as many services as required, implementing Service interface shown above. Spring will automatically add to our ServiceFactory.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.horazmakes.servicefactorytest.impl;\n\nimport org.springframework.stereotype.Component;\n\nimport com.horazmakes.servicefactorytest.Service;\n\nimport lombok.extern.slf4j.Slf4j;\n\n@Slf4j\n@Component\npublic class ServiceA implements Service {\n\n\t@Override\n\tpublic void process() {\n\t\tlog.info(\"Processing with ServiceA...\");\n\n\t}\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"ServiceA\";\n\t}\n\n}<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.horazmakes.servicefactorytest.impl;\n\nimport org.springframework.stereotype.Component;\n\nimport com.horazmakes.servicefactorytest.Service;\n\nimport lombok.extern.slf4j.Slf4j;\n\n@Slf4j\n@Component\npublic class ServiceB implements Service {\n\n\t@Override\n\tpublic void process() {\n\t\tlog.info(\"Processing with ServiceB...\");\n\n\t}\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"ServiceB\";\n\t}\n\n}<\/pre>\n\n\n\n<p>As you can see we have to annotate our services with @Component and implement Service interface.<\/p>\n\n\n\n<p><strong>Using the ServiceFactory<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.horazmakes.servicefactorytest.controller;\n\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport com.horazmakes.servicefactorytest.Service;\nimport com.horazmakes.servicefactorytest.ServiceFactory;\n\n@RestController\n@RequestMapping(\"\/service-factory-test\")\npublic class ServiceFactoryTest {\n\t\n\tprivate ServiceFactory serviceFactory;\n\t\n\tpublic ServiceFactoryTest(ServiceFactory serviceFactory) {\n\t\tthis.serviceFactory = serviceFactory;\n\t}\n\n\t@GetMapping(\"\/process\/{serviceName}\")\n\tpublic ResponseEntity&lt;Void> process(@PathVariable(name = \"serviceName\") String serviceName) {\n\t\ttry {\n\t\t\tService service = serviceFactory.getServiceByName(serviceName);\n\t\t\tservice.process();\n\t\t\t\n\t\t\treturn new ResponseEntity&lt;Void>(HttpStatus.OK);\n\t\t} catch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\treturn new ResponseEntity&lt;Void>(HttpStatus.INTERNAL_SERVER_ERROR);\n\t\t}\n\t}\n\t\n}\n<\/pre>\n\n\n\n<p>In order to use our ServiceFactory we have to inject the Spring bean, then we can get services dynamically, by their name.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Below we have a nice decoupled Service Factory example in Java, using Spring Boot. What is very cool about below approach is that you can add as many service implementations as required by declaring a new Spring component that implements your Service interface. No need to update the Service Factory itself. Service Interface This [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":437,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9,86],"tags":[160,158,162,163,159,161,92,136,70],"class_list":["post-434","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-java","category-programming","tag-best-practices","tag-boot","tag-design-pattern","tag-design-patterns","tag-factory","tag-implementation","tag-java","tag-programming","tag-spring"],"_links":{"self":[{"href":"https:\/\/horazmakes.com\/blog\/wp-json\/wp\/v2\/posts\/434","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/horazmakes.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/horazmakes.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/horazmakes.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/horazmakes.com\/blog\/wp-json\/wp\/v2\/comments?post=434"}],"version-history":[{"count":5,"href":"https:\/\/horazmakes.com\/blog\/wp-json\/wp\/v2\/posts\/434\/revisions"}],"predecessor-version":[{"id":440,"href":"https:\/\/horazmakes.com\/blog\/wp-json\/wp\/v2\/posts\/434\/revisions\/440"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/horazmakes.com\/blog\/wp-json\/wp\/v2\/media\/437"}],"wp:attachment":[{"href":"https:\/\/horazmakes.com\/blog\/wp-json\/wp\/v2\/media?parent=434"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/horazmakes.com\/blog\/wp-json\/wp\/v2\/categories?post=434"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/horazmakes.com\/blog\/wp-json\/wp\/v2\/tags?post=434"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}