5

I am currently starting a new project with a 4-tier architecture design. The layers is set as follow.

                                          +------------------+
                         +----------------+   Presentation   |
                         |                +------------------+
                         |                         |
                         |                         |
                         |         +---------------v----------------+
                         |         |                                |
                         |         |         Services (dll)         |
                         |         |                                |
                         |   +-----+  +------+  +------+  +------+  |
                         |   |     |  | Log  <-->Print <--+Other |  |
                         |   |     |  +------+  +------+  +------+  |
                         |   |     |                                |
                         |   |     +--------------------------------+
                         |   |                     |
                         |   |                     |
                         |   |            +--------v---------+
                         |   |   +--------+  Business Logic  |
                         |   |   |        +------------------+
                         |   |   |                 |
                        +v---v---v+                |
                        |         |       +--------v---------+
                        |   DTO   <-------+   Data Access    |
                        |         |       +------------------+
                        +---------+

In service Layer, I encountered a circular referencing problem as

  1. Print Service references Log service when it need to log the print task down.
  2. Log Service references Print Servicewhen it need to get some printing detail via some method in Print Service

All of the services and other tiers/layers is existed as a csproj, so it will be compiled as a dll.

I would like to know how can I get rid of such scenario? After doing some research, the most possible solution is to create an interface project for each service. Is there any other better solution?

If I really adopt interface projects approach as a solution, Does it mean that I must go along with Dependency Injection? Dependency Injection is powerful, but it makes codes to be more difficult to trace too!

Lastly, as Presentation Layer cannot reference BLL directly. Therefore, some classes and models defined in BLL cannot be referenced. Is it a good idea to store those classes and models in DTO project so that it can be used by upper-indirectly-referenced layers? (Of course I will rename the DTO project to some more meaningful name e.g. BusinessObjects)

UPDATE 1

I know my example may not be good enough. Let me change the requirement.

CourseService and StudentService this time. The dependency is visualized as follow

                            +--------------------------------+
                            |                                |
                            |         Services (DLL)         |
                            |                                |
                            |   +-------+        +-------+   |
                            |   |Course <-------->Student|   |
                            |   +-------+        +-------+   |
                            |                                |
                            +--------------------------------+
  1. CourseService has a method GetEligibleStudent(Course crs) to list Student[] those are qualified to enroll certain Course base on some course-specific information and complex logic in StudentService.
  2. StudentService need to call PrintGrade(Course crs, Student stu) to retrieve certain course grade of the a student.

Seems this example is better. I know someone may challenge that GetEligibleStudent(Course crs) should be placed in StudentService or PrintGrade(Course crs, Student stu) should be placed in CourseService. However, circular dependency may exist anytime in development. An appropriate solution should be took, not just in my example.

mannok
  • 189
  • 5
  • why would log service need printing details? to do formatting? if yes, it shouldn't, that's the print service's job. if for the call in 1 - pass all necessary data, or pass a reference to the print service object, so the log service can question it. but I don't see for what it would need that. – Aganju Nov 17 '18 at 21:58
  • Thanks for your attention. I get your point. Maybe this example is not good enough. I just want to bring out how do I handle such circular dependency case. I try to use a better example instead. – mannok Nov 17 '18 at 22:02
  • @Aganju Btw, did you mean services referencing should always follow a single way referencing pattern and I need to define the referencing priority of each of them in the beginning? – mannok Nov 17 '18 at 22:14
  • 2
    The most simple solution seems to have an interface. – Konrads Nov 17 '18 at 23:48
  • 1
    I think solution would be remove `Print` dependency from the `Log` service. `Print` service required `Log` and will provide all details to the `Log`, without `Log` service knowing about `Print` – Fabio Nov 18 '18 at 00:37
  • 2
    Another approach is to follow "rule" that when you have circular dependency you will introduce third separated library which will combine two others. – Fabio Nov 18 '18 at 01:01
  • @Konrads If I use "interface approach", does it mean that I should go with DI? – mannok Nov 18 '18 at 04:23
  • @Fabio I have thought about this solution. However, if I go with "introduce third separated library", many and many project will be created as there may have different combination of dependency between services and we further need to face triangular dependency and so on. This makes the solution become so messy as a result. – mannok Nov 18 '18 at 04:26
  • @mannok, the fact that you have many circular dependencies is the prove that you already have "messy" result ;) you should build architecture without circular dependencies in the first place. And interface workaround will make little bit more messy. Just a notice that on mine opinion Dependency Injection will make your code simpler on all levels. Possibly entry point of application can go "messy" where you register all dependencies. ;) – Fabio Nov 18 '18 at 05:02
  • You need to revise your question again as your new example doesn't make it clear at all. It looks like "Course" and "Student" in your diagram are short for CourseService & StudentService, while Course and Student in the method signatures are some other classes. In your description, you say that CourseService depends on some "complex logic in StudentService", so it presumably depends on StudentService, but the dependency in your diagram is shown the other way around. Also, the description of StudentService does not indicate clearly any dependency on CourseService. – Filip Milovanović Nov 18 '18 at 05:37
  • BTW, what does your "Business Logic" layer *do*? Does it have any business behavior, or is your business logic really in the Services layer? – Filip Milovanović Nov 18 '18 at 05:38
  • _"Dependency Injection is powerful, but it makes codes to be more difficult to trace too!"_ I know it feels that way. It felt like that to me too when I was introduced to DI and my usual click-through-the-code approach didn't quite work the way it used to anymore. But DI doesn't actually make code harder to trace, you just need to learn a different way to browse the codebase, and it's hardly any different (i.e. finding implementations of an interface is the only extra step). – Flater Sep 23 '21 at 08:17

1 Answers1

2

There is a clear data relationship of Course with Student. That's where primary domain modelling should terminate. If not, it infects design decision making for the worse.

Orchestration instead

Generally people forget about "orchestration" (or they were not taught it). If you stick with entity thinking, all you do is model entities everywhere. Entities are data, but you need to deal with "information".

So in this case:

  • Course is an entity
  • Student is an entity
  • GetEligibleStudents becomes - GetEligibleStudents(int CourseId) in an Orchestration service called something like CoursePolicy
  • PrintGrade become - PrintToPDF(int CourseId, int StudentId) in an Orchestration service called something like GradeResult

There are also potential benefits in modelling: CoursePolicy as an Entity - where the policy configuration can also be stored; and, GradeResult - where the Grade stores the grade result itself and can be retrieved in different forms. But we only consider this now, because we first considered "orchestration" and broke away from the original entities.

This applies in many other cases too of course.

Kind Contributor
  • 802
  • 4
  • 12