Invoca un repository da un altro odore di codice?

0

Recentemente mi sono imbattuto in un vecchio progetto webapp che utilizza un livello di repository tra dominio e livello di persistenza. Tuttavia, il livello del repository è stato usato in modo strano.

Un esempio è la sezione blog in cui vengono mantenuti post e commenti. Un Post ha molti Comment s.

public class Post {
    //..
    private List<Comment> comments;
    //..
}

Post e Comment risiedono in tabelle separate. Il post e i suoi commenti sono mappati usando una tabella intermedia.

|---------------------|------------------|
|      post_id        |     comment_id   |
|---------------------|------------------|
|         200         |        854       |
|---------------------|------------------|
|         200         |        931       |
|---------------------|------------------|
|        .....        |        ...       |
|---------------------|------------------|

Gli oggetti vengono mantenuti attraverso il metodo PostRepository save() .

public class PostRepository {

    public void save(Post post) {
        // db stuff here
        // save it
        String postId = // get the postId

        List<String> commentIds = (new CommentRepository()).save(post.getComments());

        for(String commentId : commentIds) {
            saveMapping(postId, commentId);
        }
    }

    private void saveMapping(String postId, String commentId) {
        // insert in the mapping table
    }
}

Tuttavia, potrei sbagliarmi, ma a me sembra che si tratti di una violazione di SRP, poiché PostRepository è stato assegnato per chiamare CommentRepository . Ciò rende anche sia PostRepository che CommentRepository consapevoli della reciproca esistenza. Non è questo un odore di codice?

Quali sono i refactoring che consiglieresti?

    
posta Shamil 06.06.2018 - 21:30
fonte

1 risposta

0

La vera domanda è: perché avere un repository separato per i commenti? Un repository fa da intermediario tra la business logic e un qualche tipo di storage o origine dati. Ma questo repository di commenti si trova tra un altro repository e l'archiviazione. Il modo in cui vengono memorizzati i commenti è solo un dettaglio di implementazione del repository Posta?

È possibile che la risposta sia no e che sia necessario un repository di commenti separato. Ciò indica che parti della logica aziendale trattano i commenti indipendentemente dal loro post principale e che la tua entità post non dovrebbe contenere un elenco di commenti. Invece, i commenti potevano conoscere direttamente i loro post padre. Per esempio. questo design ti consente di aggiornare Post senza dover salvare nuovamente tutti i commenti esistenti su quel Post.

Se si dispone di un repository di commenti separato e il repository di posta deve utilizzarlo, questa dovrebbe essere una dipendenza esplicita. Cioè la chiamata di new CommentRepository() è abbastanza dubbia. Nella maggior parte dei casi il repository sarà definito come un'interfaccia e non dovresti creare un'istanza diretta di una classe di implementazione.

Se manterremo il tuo design originale dei Post, prenderei in considerazione la possibilità di scrivere il repository in questo modo:

interface CommentRepository {
  void save(Comment comment);
  ...
}

interface PostRepository {
  void save(Post post);
}

class PostRepositoryForDatabase implements PostRepository {
  private CommentRepository commentRepo;

  public PostRepositoryForDatabase(CommentRepository commentRepo) {
    this.commentRepo = commentRepo;
  }

  @Override
  public void save(Post post) {
    ... // save post body & metadata
    for (Comment c : post.getComments()) {
      commentRepo.save(c);
      saveMapping(post, c);
    }
  }

  private void saveMapping(Post post, Comment comment) { ... }
}

L'utilizzo di un repository separato come questo è per lo più soddisfacente, poiché tale dipendenza è ora esplicita. Questo non è ancora ottimo (perché il design Post / Comment nel tuo modello di dominio e nel tuo database è un po 'insolito), ma questi aspetti sono molto più difficili da risolvere.

Se si tratta di un campo verde, un tipico progetto di post / commento con repository potrebbe essere simile a:

class Post { ... }
class Comment { Post post; ... }

interface PostRepository {
  void save(Post post);
  void saveComment(Comment comment);
  ...
}

class PostRepositoryForDatabase implements PostRepository {
  @Override
  public void save(Post post) {
    ...
  }

  @Override
  public void saveComment(Comment comment) {
    ...
  }
}

Con uno schema del database

posts(id, ...)
comments(id, post_id → posts(id), ...)

vale a dire. la tabella di mappatura non è necessaria a meno che la relazione Messaggi ai commenti non sia molti a molti. Per questa relazione da uno a molti, la chiave esterna post_id può far parte della tabella comments .

Non è strettamente necessario disporre di repository separati per post e commenti poiché ritengo che il repository sia un'astrazione sulla stessa origine dati. Spesso vengono visualizzate classi separate in C # (che ereditano da un'interfaccia generica Repository<T> ) ma questa è una comodità specifica della lingua correlata a funzioni come reflection, LINQ e metodi di estensione.

Nel mio design alternativo, in che modo la logica di business aggiunge un commento a un post? Basta creare un commento per quel post e salvarlo:

Comment c = new Comment(post, user, body);
repo.saveComment(c);

Se abbiamo bisogno di un elenco di tutti i commenti su un post, il repository dovrebbe fornire un metodo appropriato:

interface PostRepository {
  ...
  List<Comment> getCommentsForPost(Post p);
}

vale a dire. l'entità Post non esporrà un metodo come getComments() .

    
risposta data 09.06.2018 - 15:56
fonte

Leggi altre domande sui tag