Below is some code from a book which shows how cyclic dependencies:
public interface IAuditTrailAppender {
void Append(Entity changedEntity);
}
public class SqlAuditTrailAppender : IAuditTrailAppender {
private readonly IUserContext userContext;
private readonly CommerceContext context;
private readonly ITimeProvider timeProvider;
public SqlAuditTrailAppender(IUserContext userContext, CommerceContext context, ITimeProvider timeProvider) {
this.userContext = userContext;
this.context = context;
this.timeProvider = timeProvider;
}
public void Append(Entity changedEntity) {
AuditEntry entry = new AuditEntry {
UserId = this.userContext.CurrentUser.Id,
TimeOfChange = this.timeProvider.Now,
EntityId = entity.Id,
EntityType = entity.GetType().Name
};
this.context.AuditEntries.Add(entry);
}
}
public class AspNetUserContextAdapter : IUserContext {
private static HttpContextAccessor Accessor = new HttpContextAccessor();
private readonly IUserRepository repository;
public AspNetUserContextAdapter(IUserRepository repository) {
this.repository = repository;
}
public User CurrentUser {
get {
var user = Accessor.HttpContext.User;
string userName = user.Identity.Name;
return this.repository.GetByName(userName);
}
}
}
public class SqlUserRepository : IUserRepository {
public SqlUserRepository(CommerceContext context, IAuditTrailAppender appender) {
this.appender = appender;
this.context = context;
}
public void Update(User user) {
this.appender.Append(user);
}
public User GetById(Guid id) { ... }
public User GetByName(string name) { ... } // <--- used by CurrentUser property of AspNetUserContextAdapter
}
You can see cyclic dependencies exist as the picture below shows:
The author says "these kind of dependency cycles are typically caused by single-responsibility principle (SRP) violation. To fix it, the author adds a new interface IUserByNameRetriever
:
public interface IUserByNameRetriever {
User GetByName(string name);
}
public class SqlUserByNameRetriever : IUserByNameRetriever {
public SqlUserByNameRetriever(CommerceContext context) {
this.context = context;
}
public User GetByName(string name) { ... }
}
public class SqlUserRepository : IUserRepository {
public SqlUserRepository(CommerceContext context, IAuditTrailAppender appender) {
this.appender = appender;
this.context = context;
}
public void Update(User user) {
this.appender.Append(user);
}
public User GetById(Guid id) { ... }
// public User GetByName(string name) { ... } don't need this method anymore
}
public class AspNetUserContextAdapter : IUserContext {
private static HttpContextAccessor Accessor = new HttpContextAccessor();
private readonly IUserByNameRetriever retriever;
public AspNetUserContextAdapter(IUserByNameRetriever retriever) {
this.retriever = retriever;
}
public User CurrentUser {
get {
var user = Accessor.HttpContext.User;
string userName = user.Identity.Name;
return this.retriever.GetByName(userName);
}
}
}
I can understand how the introduce of IAuditTrailAppender
stops the dependency cycles, but I feel like it is just a workaround. I don't understand why the author said this dependency cycle is caused by SRP violation. Because if you look at SqlUserRepository
, its GetByName
method is a nature to have it in the class just like GetById
method (consumers can search a user by its id, and of course, it is nature for consumers to search a user by name), I can't see why having GetById
method in SqlUserRepository
is a SRP violation?
Aucun commentaire:
Enregistrer un commentaire