Hi,
Could someone please explain in simple words, what is the rationale for splitting core modules into CS (for CRUD only, I would actually call it DL for data layer) and BL (for any logic above which is more than just CRUD).
This is a practice enforced now on our project by architects, and even though I admit their knowledge and experience, I still can't understand the benefit of this. I can list several obvious cons, but can not tell a single pro. I really want to understand what this is about, and maybe finally agree with this approach.
Hi Igor,
Usually, CS and BL modules are both part of the Core Layer of the 4 Layer Canvas.
CS (refers to Core Services) is usually where you define the entities for your concepts. For instance, Customer_CS should be the module where you define the entities that support Customer information. Also, like you said, these modules include CRUD actions.
BL (refers to Business Layer) is usually where you define the logic for your applications. They should be an upper sub-layer of the Core Layer. This means that these modules can aggregate several calls to CS modules.
Imagine that you have a functionality that affects 2 or more different concepts, but the concepts are not strongly connected. So, they should be in separated modules and, because of that, they have different life-cycles. This BL sub-layer will allow you to implement a logic that aggregates these different CS modules.
This split is not entirely necessary, it really depends on the projects. But I believe the reason they did it was just because scalability. Other benefit is clearly the management of the dependencies between the modules.
If you aggregate all of the logic inside CS modules, this will lead to CS modules depending on other CS modules, leading to something like this:
This means that if you want to migrate UI1 to other environment, even though you're using only logic from CS1, you will need to migrate CS2 as well.
However, if you have BL modules, you can have something like this:
This means that if you want to migrate UI1, you don't need to migrate CS2 or CS3; you only need CS1.
Hope this helps you.
Kind regards,
Rui Barradas
Rui Barradas Thanks, this makes sense, and this is very good explanation.
However, the main thing I can't accept is that this split has to go between entities and logic. It rather has to be logical or conceptual separation. In your example, you could keep all the names CS, and allow entities to appear in any of them (if needed). This would still fix the issue in the same manner, only that would be more "righteous" and more stable way. Simply cutting out logic from entities does not guarantee to even solve the particular dependency (but it makes it more possible, yes).
To me this looks like an attempt to find generic solution for all possible dependency issues, rather than treating each case specially.
This is the best explaination by Rui Barradas . I think Outsystem is recommending us to follow clean architecture pattern. We have repositories where we implement CRUD operations. There can be multiple repositories . Core Services are similar to repositories.
Then we have application layer in clean architecture pattern where we provide orchestration. it means we can call multiple repositories and implement a business use case.
Outsystem Business layer is similar to application layer of clean architecture. Business layer can implement a use case by calling multiple operations across multiple core modules.
Hope I was able to explain this analogy and my understanding.
Interesting discussion.
This is my view on the topic. Personally I don't like introducing _BL modules until I really need to. Typically that is when we start to get in trouble with circular dependencies between CS modules.
At that point an orchestrating BL layer starts to make sense. But even then I try to keep as much 'business logic' in the CS modules and NOT in the BL modules.
The big drawback I see for keeping all business logic out of the CS modules is that this makes them 'dumb' modules, purely with entities and CRUD operations. That would be the equivalent of making all entities public and NOT read-only (so publicly editable). Even though the OutSystems best practice is to make public entities read-only. Creating public CRUD server actions without business logic basically opens up your application state to the rest of the application. You lose all guarantees that your business rules will be enforced when changing your application state. You then need to rigourously check that only the BL modules use the CS module CRUD actions.
Basically you end up with what is called an anemic domain model. Which is generally considered as an anti-pattern.
That is why I always suggest to keep the business rules close to the data and to NOT expose simple CRUD actions that do not check business rules. Basically this means sticking with only the CS modules as long as possible, and only introducing BL modules when you start to get circular dependencies between CS modules. Because that is typically the time you start needing an orchestrating layer. Until that time I agree with @Igor Kirtak and I don't see the advantages of using BL modules.
Regards,
Steven
This is exactly my main point against this split also! Not the only one, but the main one. That this approach destroys encapsulation. But you explained it better than I would.
Sorry for replying to this old topic, but I saw it today, and I want to explain while this "anemic" model might be best: it prevents your business logic code from updating data in the entities using the exposed entity actions, therefore bypassing any CRUD actions that are created, which could lead to inconsistencies in the data model.
Note that there's no hard-and-fast rule that you can have only CRUD actions in the CS-layer, and there's also no rule that says that CRUD actions can only be wrappers around the exposed entity actions. You could very well have a create/update action that also does some basic sanity checks, or also creates a history entity or updates some data showing what modifications have been made, or a delete action that performs either a soft or hard delete based on some criteria, and/or archives the record in a different database.
Hi Kilian, not sure that I understood you correctly, but it's the opposite: this anemic model does not prevent from bypassing anything. It enfoces to have simple CRUD with no business logic, no validations, nothing at all. Using those is practically the same as using automatic actions or SQL commands directly. And yes, there is exactly the rule to have nothing but basic CRUD actions in CS in this approach. Even the most basic validations are not welcomed, and updating more than one entity inside one action is not an option.
Well, if anyone is prescribing to only have entities and CRUD actions in a CS module, then yes, I agree, that could be seen as too much compartimentilization, but I'm not sure OutSystems has any documentation describing exactly that?
Note also that I'd make a distinction between basic sanity checks and logging differences on the one hand, and complex business rules with interdependencies on entities in other modules on the other hand. The latter obviously shouldn't be allowed in a CS, the former is fine with me.
I also didn't find anything to support this in Outsystems documentation, but regardless of this, the idea of this split has been promoted many times by different people like a well-known best practice, which you need to follow by default. I also saw outsystems teams following it.
Also, even if you allow some business logic in CS - it still remains questionable. What is the point of doing such split anyway? Please note that I am not talking about some complex orchestration logic combining many concepts (this I would prefer in separate module myself) - the approach is to split every core service module into CS and BL, even if there are 2 entities and 5 simple actions, it's just mandated to always have this pair.
Hi!
I see one, business logic is independent from which database you use,in a big project have the 2 in separate eSpaces can be important.
Igor, why don't you ask the architects on your project to explain this to you? It should not be a problrm for them explaining the reasoning behind their Architectural guidelines.
Daniel
Hi again Igor,
For me, that separation only makes sense if it is logic vs concept. Because logic can aggregate several concepts, meaning that one logic action can obviously impact different concepts. And each concept should be independent and have a different life-cycle (unless we're talking about strongly connected concepts).
But a concept is directly related to the entities that support that concept.
Of course, there would be another ways to architect this. But yes, they are probably trying to find a solution that prevents all kinds of dependency issues. Like I said before, scalability :)
I could see many approaches during some projects I worked, however, this is my point of view:
- simple systems can maintain the business logic into the basic core modules
- complex systems need an upper level (BL) to manage the process and deadlock between tables.
The need of BL will depend on the strategy you will use to create the core modules. There are developers that
- use separate modules for each entity
- organize entities according responsibility
- organize the entities according the domain
- use Master Data Management(MDM) approach
If you have the architecture, possibly you can see the strategy and how the system will be integrated. It will help you to understand my explanations.
Hi, guys!
Sorry to revive this discussion, but I believe it's still very relevant.
Teams are doing what Igor said, following the always-split and don't look around paradigm!
What Kilian, Steven and Igor said is very important. BL modules should be created only when necessary. We work in an agile/lean environment, so, it's expected that some controlled refactoring will appear somewhere in the product development.
I agree with Rui when he says that BL's are needed only in some occasions:
"Imagine that you have a functionality that affects 2 or more different concepts, but the concepts are not strongly connected. So, they should be in separated modules and, because of that, they have different life-cycles. This BL sub-layer will allow you to implement a logic that aggregates these different CS modules."
Indeed, the BL will isolate its consumers, as end-user components, from changes in services being consumed!
--
But, what is happening today in some companies/teams is that teams are (I believe) wrongly creating CS modules exposing CRUD-wrapper-actions without any control, no business rule or care about the check-role function.
As Steven said, it would be much easier to "make all entities public and NOT read-only (so publicly editable)."
This practice is not only out of good practices, but I believe it is actually wrong! If you are going to make something public, it should be protected and consistent! You may say that the team knows they shouldn't point their end user-modules to CS actions, but the next team that will support the app probably won't know that (which is also a problem of another kind: bad communication, documentation or poor knowledge base)! Or the team may know the right way, but made a mistake or was pressed by the sprint deadline.
Then Daniel said to follow what the architects are dictating teams to do. That's clearly right! But, in many companies, there are no architects and, worst, some architects are using the always-split rule without care or explanation (are they really architects?).
Some boot camps are teaching that: expose CRUD actions in CS modules and create BL to consume them.
Here, where I work, we have an environment with more than 40 dev teams, and we are discussing about this split. Many teams adopted this always-split practice and are now suffering with a flood of apps and modules to manage.
And, since we write automated BDD tests for every public action, teams are kind of doubling the effort with test automation and, worst and sadder, they are building fake tests for the unprotected CS actions. The real scenarios testing the BL actions in all its possibilities are being built correctly, but fake tests are referencing the CS actions with any data/context (not following business rules, as the CS action is raw) just to say the action is being tested and the test coverage rate remains good.
We will try to define the rule that suggests the BL modules and actions should be defined only when necessary!
1 Keep as much business rules as possible in CS,
2 avoid making and action public when it is not protected against unauthorized access and data loss/corruption/consistency (and others),
3 create a BL module when you need to orchestrate between different modules (of different concepts),
4 avoid defining BL and CS modules in different applications when their main concepts are the same. Do this only if needed, such as when they may have different lifecycles or different main concepts,
5 try to keep it simple! Use common sense. Do you really need to create a module because of just one or two actions that orchestrates between your CS and another CS module?
I would appreciate it if you guys could remark this post and help us set a good set of rules for this context.
huh? "But even then I try to keep as much 'business logic' in the CS modules and NOT in the BL modules."
NEVER! THis is completely wrong! CS modules are only for CRUD wrappers!! BL modules is the ones where ALL BUSINESS LOGIC must go!!!! If I see any BL in CS I cry a lot!!! It should be a finding in studio! (but very difficult to detect)