1

I'm looking for a design pattern or convention to decouple services that handle owned entities. Let's say I have a ThemeService, which handles creating Themes. At first, ThemeService just persists Themes for each user's UserData, but requirements change and Themes are owned by other entities, like a ThemeCollection. My problem is, each ThemeService is tightly coupled to whatever their "owning" entity is. For example:

public class ThemeService{
    //coupled to UserData
    createTheme(Theme t, UserData u);
    getTheme(String name, UserData u);
    hasTheme(String name, Userdata u); //Theme name unique within a userdata.
    validateTheme(Theme t, UserData u); //unique name per user, valid colors, etc.
}

public class UserDataService{
   ThemeService tService; //component for themes

   getUsername(UserData u);
   addTheme(Theme t, UserData u){ tService.createTheme(t, u); }
   getTheme(String name, UserData u){ tService.getTheme(name, u); }
   hasThemes(String name, UserData u){ tService.hasTheme(name, u); }
}

Now ThemeService is tightly coupled to the UserData. If requirements ever change and Themes can belong to another entity, ThemeCollection for example, then I can't really re-use much of the code from the ThemeService, and now need more code for ThemeCollection stuff:

public class ThemeService{
    //...continued or in another ThemeService class...
    createTheme(Theme t, ThemeCollection c);
    getTheme(String name, ThemeCollection c);
    hasTheme(String name, ThemeCollection c);
    validateTheme(Theme t, ThemeCollection c);
}

public class ThemeCollectionService{
   ThemeService tService;

   getCollectionName(ThemeCollection c);
   addTheme(Theme t, ThemeCollection c){ tService.createTheme(t, c); }
   getTheme(String name, ThemeCollection c){ tService.getTheme(name, c); }
   hasThemes(String name, ThemeCollection c){ tService.hasTheme(name, c); }
}

I would be tempted to make it take a generic parameter that implements something like "Themeable." However, that would make the entities implement an interface:

public class ThemeService{
    createTheme(Theme t, Themeable owner);
    getTheme(String name, Themeable owner);
    hasTheme(String name, Themeable owner);
    validateTheme(Theme t, Themeable owner);
}

@Entity
public class UserData implements Themeable{
   getUsername();
   getThemes(); //From Themable
}

@Entity
public class ThemeCollection implements Themeable{
   getUsername();
   getThemes(); //From Themable
}

I don't include the create, get, validate, etc. in the Themeable interface because I don't want business logic in my entity classes which should hopefully be a pure data structure (having business logic in the model is sloppy according to Robert Martin's "Clean Code", and I'm trying to follow some standard).

Is there a standard way, pattern, convention, etc., to decouple this? Is what I have more or less "okay" or is it frowned upon in a production environment? I'm trying to get away from code that "gets the job done" and towards modular and reusable code, so any help and pointers are greatly appreciated.

Edit: "Why are your services coupled to two entities?"

I need a place to 'stitch' the owning entity and the owned entities together. For example, createTheme for a UserData:

public void createTheme(Theme t, UserData u){
   entityManager.persist(t);

   if(!hasTheme(t.name(), u){
      u.getThemes().add(t);
      entityManager.merge(u);
   }
}

So this function is coupled to UserData, and any similar "Theme Owners" would have similar code.

GuitarStrum
  • 713
  • 8
  • 24
  • Why are your services coupled to two entities? Can you post some code to reveal the reason for this? – Niklas P Feb 24 '17 at 06:17
  • @NiklasP I've added a short snippet to the end of the question. – GuitarStrum Feb 24 '17 at 12:35
  • This doesn't quite answer the question. If `UserData` indeed **owns** a `Theme`, why would adding a new theme involve anything other than updating the `UserData` entity? The 'stitching' you mentioned should probably be done in the `UserDataService`. – crizzis Feb 25 '17 at 19:00
  • What is creating a tight coupling in your code is the `UserDataService`'s reference to `ThemeService`. Each service method should really correspond to a single business logic operation whether it involves modifying one or more data entities. Therefore, there is rarely a need for one service to depend on another (at the same time, multiple **repositories** might be needed to achieve the business goal; but these should not contain any logic other than that involved in persisting and removing entities). – crizzis Feb 25 '17 at 19:00
  • @crizzis To Post 1: I wanted to keep the Theme logic closer to being a component. The UserData owns a Theme, but maybe the Theme will be owned by something else in the future. To Post 2: I was trying to split out the ThemeService logic from UserDataService because UserDataService will become a large class (i.e., a user will have themes, bookmarks, settings, etc.), but I guess I should have a UserThemeService in that case, to specifically stitch users and themes together? – GuitarStrum Feb 26 '17 at 05:30

1 Answers1

1

A good approach would be separating the concept of theme collection from user. Then you have:

Class Theme
Class ThemeCollection  //all theme managment things go here
Class UserData  //has a member of type ThemeCollection

In these way, functionalites related to managing Theme are in ThemeCollection and can be shared between different entities.

sara
  • 1,130
  • 2
  • 11
  • 20
  • My problem with that is that ThemeCollection is also just a data structure. ThemeCollection would need to know how to persist itself and merge its owner, which the ServiceLayer abstracts from the data structure. The Service to stitch the ThemeCollection to its owner on the database side still has the coupling problem where it has to 'know' its owner to merge it. – GuitarStrum Feb 24 '17 at 13:53
  • When adding a new Theme to ThemeCollection, the owner dose not need to update itself. Because, it has already persisted its relation with the ThemeCollection and do not care about what is inside it. Only ThemeCollection will be merged. – sara Feb 25 '17 at 01:11
  • Yes, but what I meant in my previous comment is that now ThemeCollection is in the same position that Theme was in: ThemeCollection still needs to be tied together to the UserData, and potentially other entities in the future. – GuitarStrum Feb 26 '17 at 05:33