{"id":461103,"date":"2025-05-26T21:01:33","date_gmt":"2025-05-26T21:01:33","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=461103"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=461103","title":{"rendered":"<span>Designing profitable software: architecture principles for business success<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<h2>Business objectives and technical implications<\/h2>\n<p>We can see that a lot of new fancy frameworks and solutions appears each year, but instead looking for something new and popular that should magically solve all our problems let\u201ds stop for a bit and try to define our problems and what we actually need. The goal is of course to maximise the company profit. So, what will help to achieve it from the software side: <\/p>\n<ul>\n<li>\n<p>Ideally developers should focus only on business logic (boring but it is only what brings money) <\/p>\n<\/li>\n<li>\n<p>As little as possible technical challenges (junior developer should be able to do most of the work as good as a senior)<\/p>\n<\/li>\n<li>\n<p>Easy to understand, maintain and extend (longer to onboard people or higher qualifications required\u00a0\u2014 less profit)<\/p>\n<\/li>\n<li>\n<p>Easy to test to ensure quality<\/p>\n<\/li>\n<li>\n<p>Possibility for horizontal scalability for growing number of clients <\/p>\n<\/li>\n<\/ul>\n<p>For the beginning, it\u201ds important to understand that there is no silver bullet for 100% of the cases. So, let\u201ds define our scope first, or better what will be out of scope. We will consider remote multi\u2011client system with no low latency requirement (up to 500ms worst case per user request should be acceptable).<\/p>\n<h2>System architecture overview<\/h2>\n<p>Let\u201ds think about the architecture. Monoliths are quite complicated (at least when the application starts growing) and poorly scalable. Thus, we need distributed system, but to make it simple and easy to maintain will take anemic immutable model for implementation (will talk in detail a bit later). And if we can make services stateless, it will provide easy horizontal scalability.<\/p>\n<p>Our system can interact with 3rd party systems and that interaction can constantly change (for example, if we\u201dre developing store management system and it must interact with supplier systems. Suppliers can be changed several times per year). Thus, we need clear separation between our domain and integration layer. No 3rd party models should be used in our domain services. Otherwise, each change of 3rd party will result in the refactoring of the whole system that we definitely want to avoid.<\/p>\n<p>Other point to consider, is that we want single auth for all the system services for better user experience and to avoid coping same auth logic to each of the services. For that we can introduce api\u2011gateway service as single\u2011entry point for the external calls.<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/947\/8b9\/13d\/9478b913de57d6c8f2ff40ea6da91e89.jpg\" width=\"1581\" height=\"511\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/947\/8b9\/13d\/9478b913de57d6c8f2ff40ea6da91e89.jpg 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/947\/8b9\/13d\/9478b913de57d6c8f2ff40ea6da91e89.jpg 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>On the diagram we have connectors\u00a0\u2014 the goal of this services is to collect data from the 3rd party and transform to our unified domain model (and in opposite direction). One connector per one 3rd party system. It also should allow our system to continue working if some of the connections are down (some flows of course will not be acceptable, but it won\u201dt affect others). The connectors don\u201dt have any DB and don\u201dt persist anything, they are responsible only for transferring and transforming the data.<\/p>\n<p>Other components on the diagram:<\/p>\n<ul>\n<li>\n<p>API Gateway\u00a0\u2014 Central entry point handling auth, routing, and rate limiting<\/p>\n<\/li>\n<li>\n<p>Core Services\u00a0\u2014 Stateless business logic components<\/p>\n<\/li>\n<li>\n<p>Mapping Service\u00a0\u2014 Centralized transformation rules repository<\/p>\n<\/li>\n<li>\n<p>Storage\u00a0\u2014 Optional database modules for cross\u2011service data<\/p>\n<\/li>\n<\/ul>\n<h2>Component Design Principles<\/h2>\n<p>Now, let\u2019s look into design of the components themselves. First, the idea is to keep them stateless with the exception of in\u2011memory cache or other infrastructure optimisations (rate limit, websocket connections, etc). Thus, with no state and be just part of the data flow (this called transaction script in DDD) it\u201ds easy to operate with immutable models, and as they can\u201dt change their state it makes sense not to keep any logic there as well. So, all models are POJOs. There are other benefits of immutable anemic models such as thread\u2011safe by design that helps easy scaling and predictable inputs\/outputs allow to simplify testing (TDD works much easy with it).<\/p>\n<p>We\u201dll split each component for modules:<\/p>\n<ul>\n<li>\n<p><strong>API<\/strong>\u00a0\u2014 module that contains only models that used for input\/output of our service, shouldn\u201dt depend on anything other that apis modules of other services<\/p>\n<\/li>\n<li>\n<p><strong>APP<\/strong>\u00a0\u2014 startup of the services and configurations. Could also have presentation layer (rest controllers for example) but no business logic.<\/p>\n<\/li>\n<li>\n<p><strong>CORE<\/strong>\u00a0\u2014 contains the business logic<\/p>\n<\/li>\n<li>\n<p><strong>STORAGE<\/strong>\u00a0\u2014 Repositories that write to DB or other storage. The reason to keep this module separate is to reuse it for several services if they need to operate with the same storage, like when you want to split input\/output flows to horizontally scale one part. Doesn\u201dt needed if service doesn\u201dt have any storage at all.<\/p>\n<\/li>\n<li>\n<p><strong>CLIENT<\/strong>\u00a0\u2014 could be more than one module. It is providing wrapper on transport layer to easy use from other services. Can\u2019t depend on service modules except API.<\/p>\n<\/li>\n<\/ul>\n<p><strong>CORE<\/strong> and <strong>APP<\/strong> modules never can be used as dependencies for other services. It\u2019s important to notice that neither whole <strong>CORE<\/strong>, nor its parts can be reused between services, as it\u2019s a business logic. Especially that is paramount for connectors as different 3rd party can behave similar. However, as it can be changed independently it can cause complications in the future if will be extracted. Never try to extract business logic to libraries &#8212; either merge the services to one if the behaviour is guaranteed to be the same or just copy\/paste otherwise.<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<td>\n<p align=\"left\"><strong><em>Module<\/em><\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong><em>Can depend on<\/em><\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong><em>Can\u2019t depend on<\/em><\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">API<\/p>\n<\/td>\n<td>\n<p align=\"left\">Other API modules <\/p>\n<\/td>\n<td>\n<p align=\"left\">Everything else<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">CLIENT<\/p>\n<\/td>\n<td>\n<p align=\"left\">API modules<\/p>\n<\/td>\n<td>\n<p align=\"left\">CORE, APP<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">CORE<\/p>\n<\/td>\n<td>\n<p align=\"left\">API, STORAGE<\/p>\n<\/td>\n<td>\n<p align=\"left\">APP, Other CORE<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<h2>Technology selection criteria<\/h2>\n<p>The system design that way can easily contain components in different languages. However, more different languages, frameworks, approaches are used \u2013 more difficult to support. So, better to limiting variety in the system.<\/p>\n<p>Thus, we need to limit what we will use for majority of our system.  Scripting languages &#8212; basically not suitable for anything other than small scripts. Dynamic types can cause lots of bugs in runtime, code become extremely difficult to maintain as changes can cause completely unexpected side effects. So, that type of languages we better avoid using in production at all. (Python, Groovy, etc)<\/p>\n<p>System language is great choice for writing software for microcontrollers, implementing OS or extreme low-latency, but they add a lot complexity, and for most goals their benefits are actually useless. Thus, it better to use them only when you do have specific requirements that they can help with, and definitely not the main choose (Zig, Rust, C\/C++, etc).<\/p>\n<p>This leave as with general propose group like Java, Kotlin, C#, etc. It better to consider language with the richest ecosystem to avoid spending time on infrastructure tasks. Also, the ecosystem should be suitable with microservices (that is the problem with C# as it designs primarily for monolith and rich data models). Of course, due to all of that, jvm based languages will be a great choice. Probably Kotlin is slightly better due to less boilerplate and more advance features. Null safety makes maintenance easy as well.<\/p>\n<p>Moreover, don\u2019t disregard huge frameworks like Spring or Micronaut as they can drastically speed up all infrastructure setup for your services. Main issue why people usually don\u2019t like them is due to number of dependencies that they bring that cause RAM overhead and some performance overhead. However, it\u2019s important to understand is that overhead makes any difference for your application or not, as in majority of applications it won\u2019t be noticeable at all. On the other hand, they provide a lot of integrations that you need out of the box, including monitoring that is necessary for any service in prod. They can save multiple weeks on developing all infrastructure.<\/p>\n<h2>Benefits<\/h2>\n<p>As example for what we are achieving by implement system that way:<\/p>\n<ul>\n<li>\n<p>Small microservices and well-known technologies reduce onboarding from couple of weeks to couple of days (that is several thousand pounds per developer)<\/p>\n<\/li>\n<li>\n<p>Simplicity of services design allow juniors to work on business logic as well efficient as seniors<\/p>\n<\/li>\n<li>\n<p>Horizontal scalability around 40% cheaper than vertical scaling<\/p>\n<\/li>\n<li>\n<p>3rd party isolation prevents 200+ hours\/year refactoring per integration<\/p>\n<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/913000\/\"> https:\/\/habr.com\/ru\/articles\/913000\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<h2>Business objectives and technical implications<\/h2>\n<p>We can see that a lot of new fancy frameworks and solutions appears each year, but instead looking for something new and popular that should magically solve all our problems let\u201ds stop for a bit and try to define our problems and what we actually need. The goal is of course to maximise the company profit. So, what will help to achieve it from the software side: <\/p>\n<ul>\n<li>\n<p>Ideally developers should focus only on business logic (boring but it is only what brings money) <\/p>\n<\/li>\n<li>\n<p>As little as possible technical challenges (junior developer should be able to do most of the work as good as a senior)<\/p>\n<\/li>\n<li>\n<p>Easy to understand, maintain and extend (longer to onboard people or higher qualifications required\u00a0\u2014 less profit)<\/p>\n<\/li>\n<li>\n<p>Easy to test to ensure quality<\/p>\n<\/li>\n<li>\n<p>Possibility for horizontal scalability for growing number of clients <\/p>\n<\/li>\n<\/ul>\n<p>For the beginning, it\u201ds important to understand that there is no silver bullet for 100% of the cases. So, let\u201ds define our scope first, or better what will be out of scope. We will consider remote multi\u2011client system with no low latency requirement (up to 500ms worst case per user request should be acceptable).<\/p>\n<h2>System architecture overview<\/h2>\n<p>Let\u201ds think about the architecture. Monoliths are quite complicated (at least when the application starts growing) and poorly scalable. Thus, we need distributed system, but to make it simple and easy to maintain will take anemic immutable model for implementation (will talk in detail a bit later). And if we can make services stateless, it will provide easy horizontal scalability.<\/p>\n<p>Our system can interact with 3rd party systems and that interaction can constantly change (for example, if we\u201dre developing store management system and it must interact with supplier systems. Suppliers can be changed several times per year). Thus, we need clear separation between our domain and integration layer. No 3rd party models should be used in our domain services. Otherwise, each change of 3rd party will result in the refactoring of the whole system that we definitely want to avoid.<\/p>\n<p>Other point to consider, is that we want single auth for all the system services for better user experience and to avoid coping same auth logic to each of the services. For that we can introduce api\u2011gateway service as single\u2011entry point for the external calls.<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>On the diagram we have connectors\u00a0\u2014 the goal of this services is to collect data from the 3rd party and transform to our unified domain model (and in opposite direction). One connector per one 3rd party system. It also should allow our system to continue working if some of the connections are down (some flows of course will not be acceptable, but it won\u201dt affect others). The connectors don\u201dt have any DB and don\u201dt persist anything, they are responsible only for transferring and transforming the data.<\/p>\n<p>Other components on the diagram:<\/p>\n<ul>\n<li>\n<p>API Gateway\u00a0\u2014 Central entry point handling auth, routing, and rate limiting<\/p>\n<\/li>\n<li>\n<p>Core Services\u00a0\u2014 Stateless business logic components<\/p>\n<\/li>\n<li>\n<p>Mapping Service\u00a0\u2014 Centralized transformation rules repository<\/p>\n<\/li>\n<li>\n<p>Storage\u00a0\u2014 Optional database modules for cross\u2011service data<\/p>\n<\/li>\n<\/ul>\n<h2>Component Design Principles<\/h2>\n<p>Now, let\u2019s look into design of the components themselves. First, the idea is to keep them stateless with the exception of in\u2011memory cache or other infrastructure optimisations (rate limit, websocket connections, etc). Thus, with no state and be just part of the data flow (this called transaction script in DDD) it\u201ds easy to operate with immutable models, and as they can\u201dt change their state it makes sense not to keep any logic there as well. So, all models are POJOs. There are other benefits of immutable anemic models such as thread\u2011safe by design that helps easy scaling and predictable inputs\/outputs allow to simplify testing (TDD works much easy with it).<\/p>\n<p>We\u201dll split each component for modules:<\/p>\n<ul>\n<li>\n<p><strong>API<\/strong>\u00a0\u2014 module that contains only models that used for input\/output of our service, shouldn\u201dt depend on anything other that apis modules of other services<\/p>\n<\/li>\n<li>\n<p><strong>APP<\/strong>\u00a0\u2014 startup of the services and configurations. Could also have presentation layer (rest controllers for example) but no business logic.<\/p>\n<\/li>\n<li>\n<p><strong>CORE<\/strong>\u00a0\u2014 contains the business logic<\/p>\n<\/li>\n<li>\n<p><strong>STORAGE<\/strong>\u00a0\u2014 Repositories that write to DB or other storage. The reason to keep this module separate is to reuse it for several services if they need to operate with the same storage, like when you want to split input\/output flows to horizontally scale one part. Doesn\u201dt needed if service doesn\u201dt have any storage at all.<\/p>\n<\/li>\n<li>\n<p><strong>CLIENT<\/strong>\u00a0\u2014 could be more than one module. It is providing wrapper on transport layer to easy use from other services. Can\u2019t depend on service modules except API.<\/p>\n<\/li>\n<\/ul>\n<p><strong>CORE<\/strong> and <strong>APP<\/strong> modules never can be used as dependencies for other services. It\u2019s important to notice that neither whole <strong>CORE<\/strong>, nor its parts can be reused between services, as it\u2019s a business logic. Especially that is paramount for connectors as different 3rd party can behave similar. However, as it can be changed independently it can cause complications in the future if will be extracted. Never try to extract business logic to libraries &#8212; either merge the services to one if the behaviour is guaranteed to be the same or just copy\/paste otherwise.<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<td>\n<p align=\"left\"><strong><em>Module<\/em><\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong><em>Can depend on<\/em><\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong><em>Can\u2019t depend on<\/em><\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">API<\/p>\n<\/td>\n<td>\n<p align=\"left\">Other API modules <\/p>\n<\/td>\n<td>\n<p align=\"left\">Everything else<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">CLIENT<\/p>\n<\/td>\n<td>\n<p align=\"left\">API modules<\/p>\n<\/td>\n<td>\n<p align=\"left\">CORE, APP<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">CORE<\/p>\n<\/td>\n<td>\n<p align=\"left\">API, STORAGE<\/p>\n<\/td>\n<td>\n<p align=\"left\">APP, Other CORE<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<h2>Technology selection criteria<\/h2>\n<p>The system design that way can easily contain components in different languages. However, more different languages, frameworks, approaches are used \u2013 more difficult to support. So, better to limiting variety in the system.<\/p>\n<p>Thus, we need to limit what we will use for majority of our system.  Scripting languages &#8212; basically not suitable for anything other than small scripts. Dynamic types can cause lots of bugs in runtime, code become extremely difficult to maintain as changes can cause completely unexpected side effects. So, that type of languages we better avoid using in production at all. (Python, Groovy, etc)<\/p>\n<p>System language is great choice for writing software for microcontrollers, implementing OS or extreme low-latency, but they add a lot complexity, and for most goals their benefits are actually useless. Thus, it better to use them only when you do have specific requirements that they can help with, and definitely not the main choose (Zig, Rust, C\/C++, etc).<\/p>\n<p>This leave as with general propose group like Java, Kotlin, C#, etc. It better to consider language with the richest ecosystem to avoid spending time on infrastructure tasks. Also, the ecosystem should be suitable with microservices (that is the problem with C# as it designs primarily for monolith and rich data models). Of course, due to all of that, jvm based languages will be a great choice. Probably Kotlin is slightly better due to less boilerplate and more advance features. Null safety makes maintenance easy as well.<\/p>\n<p>Moreover, don\u2019t disregard huge frameworks like Spring or Micronaut as they can drastically speed up all infrastructure setup for your services. Main issue why people usually don\u2019t like them is due to number of dependencies that they bring that cause RAM overhead and some performance overhead. However, it\u2019s important to understand is that overhead makes any difference for your application or not, as in majority of applications it won\u2019t be noticeable at all. On the other hand, they provide a lot of integrations that you need out of the box, including monitoring that is necessary for any service in prod. They can save multiple weeks on developing all infrastructure.<\/p>\n<h2>Benefits<\/h2>\n<p>As example for what we are achieving by implement system that way:<\/p>\n<ul>\n<li>\n<p>Small microservices and well-known technologies reduce onboarding from couple of weeks to couple of days (that is several thousand pounds per developer)<\/p>\n<\/li>\n<li>\n<p>Simplicity of services design allow juniors to work on business logic as well efficient as seniors<\/p>\n<\/li>\n<li>\n<p>Horizontal scalability around 40% cheaper than vertical scaling<\/p>\n<\/li>\n<li>\n<p>3rd party isolation prevents 200+ hours\/year refactoring per integration<\/p>\n<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/913000\/\"> https:\/\/habr.com\/ru\/articles\/913000\/<\/a><br \/><\/br><\/br><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-461103","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/461103","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=461103"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/461103\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=461103"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=461103"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=461103"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}