4

I'm trying to switch to clean architecture from a traditional layered architecture approach and trying to figure out where to put business logic. Please consider the below scenario -

Employee class (exists in Efcore db first scaffolded in core)-

class Employee {
    public int Id {get; set;}
    public int TimeWorked {get; set;}
    public int ZoneCode {get; set;}
    public datetime JoiningDate {get; set;}
    public decimal Reimbursement {get; set;}
}
class SomeMasterData { //Could be a value object
    public int ZoneCode {get; set;}
    public decimal OvertimeAllowedForZoneCode {get; set;}
}
class SomeOtherMasterData {  //Could be a value object
    public int OvertimeMultiplier {get; set;} //and whatever other props
}

Business logic related to the employee -

int CalculateReimbursement(Employee emp, SomeMasterData masterData, , SomeOtherMasterData masterData2) {
    // Use data from Employee (fields like zone code, joining date etc) and
    // data from SomeMasterData to calculate the reimbursement amount for the employee
    // The below business logic is random, created for this question
    if(emp.ZoneCode == masterData.ZoneCode && emp.JoiningDate > 'some date') {
        return masterData.OvertimeAllowedForZoneCode * masterData2.OvertimeMultiplier * emp.TimeWorked;
    }
    else //More business logic based on many entities and value objects
    ...
}

My question is - Considering Clean Architecture, where to put the CalculateReimbursement method? Below are a few options -

  1. In domain services in the core - But is this what domain services are for?
  2. In the Employee class - But that class is autogenerated by Efcore db first scaffolding so I can't change it. Should I create another Employee partial class with this method?
  3. In some sort of "helper" class within the core - if so, what do I call it?
  4. Somewhere else that makes more sense keeping Clean Architecture in mind?
  • where to put the CalculateReimbursement method? Why it should be a method? Why not a class within the core? – Laiv Apr 17 at 7:16
  • As a use case? Should that class go in a Use Case folder? Also, are we talking about a class with only this one method? – Achilles Apr 17 at 10:31
  • Some operations can be domain's classes, not necessarily use cases. Use cases are business-specific operations (high-level policies); CalculateReimbursement could be a low-level policy within the core. Not everything within code is Value Objects or entities, there could be also functions modelled as classes. – Laiv Apr 17 at 11:55
  • EF uses partial classes. You can create another partial class with the same name as the entity, and put your logic there. Clean Architecture probably precludes this approach, however. – Robert Harvey Apr 17 at 22:01
  • Why would calculation of overtime be the responsibility of an employee? In a non digital system you may ask an employee to record their hours somewhere and another accounting type employee would calculate the amount of overtime to be paid. In the digital world you should follow a similar pattern. – kiwiron Apr 18 at 7:53
4

In addition to Mike's answer and in line with the comments above.

The business knowledge encapsulated in CalculateReimbursement makes it fits in the core. More specifically, in the Entities layer.

Entities encapsulate Enterprise-wide business rules. An entity can be an object with methods, or it can be a set of data structures and functions. It doesn’t matter so long as the entities could be used by many different applications

Clean Code Blog : The Clean Architecture - R.C.Martin

The emphasis of mine.

The author doesn't say whether these functions are services, helpers or utilities because the focus is not on the implementation details, it's on the boundary: business-specific rules belong to the core.

In relation to the previous, the author stresses the idea of isolating core from implementation details such as frameworks or infrastructure.

We also do not expect this layer to be affected by changes to externalities such as the database, the UI, or any of the common frameworks. This layer is isolated from such concerns.

Clean Code Blog : The Clean Architecture - R.C.Martin

Q: But then we're talking about creating a duplicate class for each entity. Isn't that too much work when the autogenerated entities already exist?

No.

On the first place, nothing forces us to map entities 1:1. Domain and persistence data models are different abstractions. One entity of the domain might need N entities of the persistence layer or vice-versa. It's important bearing in mind that each model is designed to be written and read efficiently on their respective stores (memory and/or files). In consequence, both deal with different rules and constraints.

On the second place, the extra work is executed by the auto-generator. Isn't it? That's the only point of using such tools. Our efforts must be addressed to model and evolve the core, not the persistence. We make the persistence work for us, not the opposite.1

Finally, segregation and isolation are basic notions of clean architectures. Premises like -less LoCs make code easier and cheaper to maintain- are fallacious. Good abstractions and well-segregated concerns make code easier and cheaper to maintain.

Q: Also, extra work for manually keeping the domain version of the entities keeping in sync with any changes in the DB tables?

This's a wrong conclusion based on a wrong assumption: business depends on persistence. That's not how clean architecture works.

In clean architectures, the persistence cannot dictate how to model the business, because the business doesn't depend on persistence. It's just the opposite. At this point, I would suggest getting familiar with the notion stable dependency because it's the bedrocks of this architectural style.

Q: I understand that having a domain version of the models is ideal but just wondering if there's a solution where the autogenerated entities can be reused to reduce the manual work while also following clean architecture as closely as possible?

Matter of convenience.

To my experience, implementing well-defined architectural styles partially doesn't worth the efforts, overall when the argument for it is -autogenerated code can fulfil my needs for persistence and business logic and save me a handful of LoCs-.2

Why? Because mid-implementations lead us to nobody's land. A place where the advantages of the architecture are half-truths and half-lies at the same time. In this limbo, the LoC saving to which we appealed turns against us because it prevents us from finding the proper support to achieve the advantages of the architecture.


1: Data-centric domain models are not bad per se. Some applications have very little business logic|rules (or none at all). The most suitable approach depends on the complexity of the business requirements. For example, using the domain model for a query-and-report process is unnecessarily complicated. In cases like this, it helps to recognize simplicity for what it is, get rid of the domain layers, and gain direct access to the infrastructure layer.

2: One-fit-all solutions don't exist

6

You could use the models created by EF as purely models made for persisting your data. You should map these to an actual Employee domain model class that can hold all your business logic and that would be truly your domain models.

  • Thank you. But then we're talking about creating a duplicate class for each entity. Isn't that too much work when the autogenerated entities already exist? Also, extra work for manually keeping the domain version of the entities keeping in sync with any changes in the db tables? I understand that having a domain version of the models is ideal but just wondering if there's a solution where the autogenerated entities can be reused to reduce the manual work while also following clean architecture as closely as possible? – Achilles Apr 17 at 10:30
1

Business logic maybe always handles several entities, and should not be in the layer of generated entities.

Ideally it should be its own layer in the core. I know one case where there was formalized, versioned Word documentation describing business rules, and in the code every piece of busines code referred to the documentation, where the version number somewhat congrues with the change request ticket.

The classes of the business logic, BOs (Business Objects), are in there own layer in the core, modularized.

The frontend, GUI & controllers, determine what is needed, and what is the best form, hence one must ensure that a developer can dive into the layers' depth and still have an overview.

This means that for every use-case on every level/layer, there should be recognition, short package paths, not a too complex prescribed subhierarchy, but a clean separation of other use-cases (without code copying as standard means).

Additional artifacts, intermediate "pass-on" layers of code (as with interfaces) should be thin as possible. Best no boiler plate code. Personally I like interfaces; separation of concerns might lead to implement a component against an interface, whereas the effective class implements much more.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.