2

I'm trying to group the components of my system by funtionality. This is the analysis class diagram of my system model.

enter image description here

A service class that involves a Post entity might necessarily interact sometimes with Section, User, or also (to a lesser degree) PostVote. This also applies with the repository classes of each entity of course.

As an example, here's my PostService#newPost method:

@AuthenticationRequired //auth interceptor
public int newPost(@NotBlank String title,
                   @Size(max=65535) String body,
                   @NotNull String sectionName){
    //currentUser is an authentication related DTO
    User user = userRepository.findById(currentUser.getUsername());
    Section section = sectionRepository.findByNaturalId(sectionName);

    if(section == null)
        throw new SectionDoesntExistException();

    Post post = new Post();
    post.setTitle(title);
    post.setContent(body == null || body.isBlank() ? "" : body);
    post.setAuthor(user);
    post.setSection(section);
    post.setType(TEXT);

    return postRepository.insert(post).getId();
}  

The SectionRepository class is an included dependency that is used only to check for the Section existance and to get a managed entity. This happens in a lot of other places in my code base.

I could try of course to move this retrieval logic in PostRepository (the insert method calls SectionRepository#findByNaturalId and fails if none found), but this wouldn't answer my question: should the entities and/or its repositories be included in the package that outlines the vertical slice in my system in the first place? Or should they be put in a common entity and repository bucket that sits underneath all vertical slices in my system architecture?
The reason I'm asking this is that many see these vertical slices as independent codebases with minimal dependencies with peer subsystems. Introducing the entities in these package may add inevitably some dependencies.

What's the common practice for this kind of architectural pattern?

cidra
  • 311
  • 2
  • 9

3 Answers3

2

You are separating data from function. This does not pair well with what you're trying to do. Your class diagram contains only data structures. This is something you'll always have to touch when developing something new, or even when changing features. So changes will not be localized this way.

Based on your other post I think you can't quite get away from the remnants of the layered approach. You'll have to be much bolder. Design objects with functionality and hide the data! That's what objects are for. Try to get away from technical thinking (like data structures, repositories and services) and try to think about business behavior and how those interact.

As an exercise try to re-design these classes in a way that:

  1. No object has a getter. This mean no internal data is returned. Ever.
  2. All methods are some derivation of-, or a part of-, a business-related function / requirement.

If you can do this, you'll have a much easier time thinking about functions and behavior.

Robert Bräutigam
  • 11,473
  • 1
  • 17
  • 36
  • Thank you a lot for your answers. I've often seen the adding of behaviour in persistent entities as discouraged in favour of the addition of a data access layer: this (together with a bit of reading of the popular *Clean Architecture* book) has taken me into choosing the layered approach. The other way looks more suitable for OOP (And for the use cases I've written, also), although I haven't had luck finding good and consistent resources in regard. Do you have any resource/example in regard that i could peek into? – cidra Mar 02 '22 at 19:18
  • 1
    I'm not talking about adding behavior to "persistent entities", but having objects with behavior *instead of* "persistent entities". You're still thinking about having data and operating on data. As for resources, [here is one article of mine](https://javadevguy.wordpress.com/2017/11/15/screaming-architect/) looking at where Clean Architecture leads you. [Here is another one](https://javadevguy.wordpress.com/2017/07/27/a-detailed-analysis-of-the-clean-architecture-from-an-object-oriented-perspective/) looking at just the theory. – Robert Bräutigam Mar 02 '22 at 21:24
  • 1
    Yes, It's really hard to take off this *data-only, behaviour-only* mindset after years of having to deal with DAOs, Repository, ORMs, etc. Anyway, thanks a lot for sharing your articles, they're all really interesting – cidra Mar 03 '22 at 12:15
  • @RobertBräutigam in your second article, are you advocating for entities to persist themselves ? So `ProductRepository` would be injected in `Product` and `product.updateName(name)` would call the repository ? Active records in a sens. That point was not clear – Ced Sep 30 '22 at 19:30
  • Those articles are really interesting as I'm personally annoyed with those layers and the lack of object orientation. If you have an example repository or even an exemple entity that'd be nice. It's very interesting to hear your critics but seeing your solution is equally meaningful unfortunately it is not part of the article – Ced Sep 30 '22 at 21:49
  • Like in your presentation here https://speakerdeck.com/robertbraeutigam/object-oriented-domain-driven-design?slide=32 slide 32, you have `toJson` but there still need to be a service somewhere to call `customerRepository.save(customer)`. This call needs to be somewhere. However before you eliminate the service. So that option does not hold ? – Ced Sep 30 '22 at 23:57
1

A couple of thoughts

  1. I believe features are usually built on top of a domain. They orchestrate actions performed on entities of that domain.

    With this in mind, this shared behaviour could be placed in the domain.

  2. Features may have some common steps. But this is kind of "accidental" code duplication, meaning they currently share this actions but might not in the future.

freakmind
  • 85
  • 6
0

I going to chime in here because although I agree with the content of the other answers, I'm not so sure they actually answer your question. Namely, how the pattern you are describing might take shape in your system in a way that abstracts data in favor of behavior.

What we want here is to exercise inversion of control (to your domain) in combination with a factory pattern:

Section section = sectionRepository.findByNaturalId(sectionName);

// we are ceding control to your domain to create a new post
Post post = section.newPost(author, title, body, type); 

The signature of Section#newPost may take different forms depending on your preferences, but you get the idea.

user3347715
  • 3,084
  • 11
  • 16