Applicability of Single Responsibility PrincipleDid the Gang of Four thoroughly explore “Pattern Space”?Single Responsibility Principle ImplementationSomething confusing about Single Responsibility PrincipleSingle Responsibility Principle: Responsibility unknownIdentifying Domain Services & Application Services when doing DDDIs Domain Entity violating Single Responsibility Principle?Application of Single Responsibility Principle on a ButtonWhen using the Single Responsibility Principle, what constitutes a “responsibility?”Single Responsibility Principle Violation?Does Template pattern violate Single Responsibility principle?Single responsibility principle - importer

How does it work when somebody invests in my business?

India just shot down a satellite from the ground. At what altitude range is the resulting debris field?

How to safely derail a train during transit?

How can a jailer prevent the Forge Cleric's Artisan's Blessing from being used?

Are student evaluations of teaching assistants read by others in the faculty?

How to Reset Passwords on Multiple Websites Easily?

Why escape if the_content isnt?

I'm in charge of equipment buying but no one's ever happy with what I choose. How to fix this?

How can we prove that any integral in the set of non-elementary integrals cannot be expressed in the form of elementary functions?

Closest Prime Number

Unexpected indention in bibliography items (beamer)

How did Doctor Strange see the winning outcome in Avengers: Infinity War?

Understanding authentication for Apex SOAP web service

Is the destination of a commercial flight important for the pilot?

What's this thing which looks like a water sensor inside a PC mouse?

How can I get through very long and very dry, but also very useful technical documents when learning a new tool?

How easy is it to start Magic from scratch?

If you attempt to grapple an opponent that you are hidden from, do they roll at disadvantage?

How can I kill an app using Terminal?

What is the intuitive meaning of having a linear relationship between the logs of two variables?

when is out of tune ok?

QGIS Polygon Selection

Italian words for tools

Short story about space worker geeks who zone out by 'listening' to radiation from stars



Applicability of Single Responsibility Principle


Did the Gang of Four thoroughly explore “Pattern Space”?Single Responsibility Principle ImplementationSomething confusing about Single Responsibility PrincipleSingle Responsibility Principle: Responsibility unknownIdentifying Domain Services & Application Services when doing DDDIs Domain Entity violating Single Responsibility Principle?Application of Single Responsibility Principle on a ButtonWhen using the Single Responsibility Principle, what constitutes a “responsibility?”Single Responsibility Principle Violation?Does Template pattern violate Single Responsibility principle?Single responsibility principle - importer













28

















I recently came by a seemingly trivial architectural problem. I had a simple repository in my code that was called like this (code is in C#):



var user = /* create user somehow */;
_userRepository.Add(user);
/* do some other stuff*/
_userRepository.SaveChanges();


SaveChanges was a simple wrapper that commits changes to database:



void SaveChanges()

_dataContext.SaveChanges();
_logger.Log("User DB updated: " + someImportantInfo);



Then, after some time, I needed to implement new logic that would send email notifications every time a user was created in the system. Since there were many calls to _userRepository.Add() and SaveChanges around the system, I decided to update SaveChanges like this:



void SaveChanges()

_dataContext.SaveChanges();
_logger.Log("User DB updated: " + someImportantInfo);
foreach (var newUser in dataContext.GetAddedUsers())

_eventService.RaiseEvent(new UserCreatedEvent(newUser ))




This way, external code could subscribe to UserCreatedEvent and handle the needed business logic that would send notifications.



But it was pointed out to me that my modification of SaveChanges violated the Single Responsibility principle, and that SaveChanges should just save and not fire any events.



Is this a valid point? It seems to me that the raising an event here is essentially the same thing as logging: just adding some side functionality to the function. And SRP does not prohibit you from using logging or firing events in your functions, it just says that such logic should be encapsulated in other classes, and it is OK for a repository to call these other classes.










share|improve this question



















  • 16





    Your retort is: "OK, so how would you write it so that it doesn't violate SRP but still allows a single point of modification?"

    – Robert Harvey
    yesterday






  • 29





    My observation is that raising an event does not add an additional responsibility. In fact, quite the opposite: it delegates the responsibility somewhere else.

    – Robert Harvey
    yesterday











  • I think your coworker is right, but your question is valid and useful, so upvoted!

    – Andres F.
    22 hours ago






  • 11





    There's no such thing as a definitive definition of a Single Responsibility. The person pointing out that it violates SRP is correct using their personal definition and you are correct using your definition. I think your design is perfectly fine with the caveat that this event isn't a one-off whereby other similar functionality is done in different ways. Consistency is far, far, far more important to pay attention to than some vague guideline like SRP which carried to the extreme ends up with tons of very easy to understand classes that nobody knows how to make work in a system.

    – Dunk
    21 hours ago











  • Does SaveChanges update multiple users?

    – Nishant
    15 hours ago















28

















I recently came by a seemingly trivial architectural problem. I had a simple repository in my code that was called like this (code is in C#):



var user = /* create user somehow */;
_userRepository.Add(user);
/* do some other stuff*/
_userRepository.SaveChanges();


SaveChanges was a simple wrapper that commits changes to database:



void SaveChanges()

_dataContext.SaveChanges();
_logger.Log("User DB updated: " + someImportantInfo);



Then, after some time, I needed to implement new logic that would send email notifications every time a user was created in the system. Since there were many calls to _userRepository.Add() and SaveChanges around the system, I decided to update SaveChanges like this:



void SaveChanges()

_dataContext.SaveChanges();
_logger.Log("User DB updated: " + someImportantInfo);
foreach (var newUser in dataContext.GetAddedUsers())

_eventService.RaiseEvent(new UserCreatedEvent(newUser ))




This way, external code could subscribe to UserCreatedEvent and handle the needed business logic that would send notifications.



But it was pointed out to me that my modification of SaveChanges violated the Single Responsibility principle, and that SaveChanges should just save and not fire any events.



Is this a valid point? It seems to me that the raising an event here is essentially the same thing as logging: just adding some side functionality to the function. And SRP does not prohibit you from using logging or firing events in your functions, it just says that such logic should be encapsulated in other classes, and it is OK for a repository to call these other classes.










share|improve this question



















  • 16





    Your retort is: "OK, so how would you write it so that it doesn't violate SRP but still allows a single point of modification?"

    – Robert Harvey
    yesterday






  • 29





    My observation is that raising an event does not add an additional responsibility. In fact, quite the opposite: it delegates the responsibility somewhere else.

    – Robert Harvey
    yesterday











  • I think your coworker is right, but your question is valid and useful, so upvoted!

    – Andres F.
    22 hours ago






  • 11





    There's no such thing as a definitive definition of a Single Responsibility. The person pointing out that it violates SRP is correct using their personal definition and you are correct using your definition. I think your design is perfectly fine with the caveat that this event isn't a one-off whereby other similar functionality is done in different ways. Consistency is far, far, far more important to pay attention to than some vague guideline like SRP which carried to the extreme ends up with tons of very easy to understand classes that nobody knows how to make work in a system.

    – Dunk
    21 hours ago











  • Does SaveChanges update multiple users?

    – Nishant
    15 hours ago













28












28








28


7








I recently came by a seemingly trivial architectural problem. I had a simple repository in my code that was called like this (code is in C#):



var user = /* create user somehow */;
_userRepository.Add(user);
/* do some other stuff*/
_userRepository.SaveChanges();


SaveChanges was a simple wrapper that commits changes to database:



void SaveChanges()

_dataContext.SaveChanges();
_logger.Log("User DB updated: " + someImportantInfo);



Then, after some time, I needed to implement new logic that would send email notifications every time a user was created in the system. Since there were many calls to _userRepository.Add() and SaveChanges around the system, I decided to update SaveChanges like this:



void SaveChanges()

_dataContext.SaveChanges();
_logger.Log("User DB updated: " + someImportantInfo);
foreach (var newUser in dataContext.GetAddedUsers())

_eventService.RaiseEvent(new UserCreatedEvent(newUser ))




This way, external code could subscribe to UserCreatedEvent and handle the needed business logic that would send notifications.



But it was pointed out to me that my modification of SaveChanges violated the Single Responsibility principle, and that SaveChanges should just save and not fire any events.



Is this a valid point? It seems to me that the raising an event here is essentially the same thing as logging: just adding some side functionality to the function. And SRP does not prohibit you from using logging or firing events in your functions, it just says that such logic should be encapsulated in other classes, and it is OK for a repository to call these other classes.










share|improve this question


















I recently came by a seemingly trivial architectural problem. I had a simple repository in my code that was called like this (code is in C#):



var user = /* create user somehow */;
_userRepository.Add(user);
/* do some other stuff*/
_userRepository.SaveChanges();


SaveChanges was a simple wrapper that commits changes to database:



void SaveChanges()

_dataContext.SaveChanges();
_logger.Log("User DB updated: " + someImportantInfo);



Then, after some time, I needed to implement new logic that would send email notifications every time a user was created in the system. Since there were many calls to _userRepository.Add() and SaveChanges around the system, I decided to update SaveChanges like this:



void SaveChanges()

_dataContext.SaveChanges();
_logger.Log("User DB updated: " + someImportantInfo);
foreach (var newUser in dataContext.GetAddedUsers())

_eventService.RaiseEvent(new UserCreatedEvent(newUser ))




This way, external code could subscribe to UserCreatedEvent and handle the needed business logic that would send notifications.



But it was pointed out to me that my modification of SaveChanges violated the Single Responsibility principle, and that SaveChanges should just save and not fire any events.



Is this a valid point? It seems to me that the raising an event here is essentially the same thing as logging: just adding some side functionality to the function. And SRP does not prohibit you from using logging or firing events in your functions, it just says that such logic should be encapsulated in other classes, and it is OK for a repository to call these other classes.







architecture single-responsibility






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 1 hour ago









Samantha Cox

1073




1073










asked yesterday









Andre BorgesAndre Borges

73611013




73611013







  • 16





    Your retort is: "OK, so how would you write it so that it doesn't violate SRP but still allows a single point of modification?"

    – Robert Harvey
    yesterday






  • 29





    My observation is that raising an event does not add an additional responsibility. In fact, quite the opposite: it delegates the responsibility somewhere else.

    – Robert Harvey
    yesterday











  • I think your coworker is right, but your question is valid and useful, so upvoted!

    – Andres F.
    22 hours ago






  • 11





    There's no such thing as a definitive definition of a Single Responsibility. The person pointing out that it violates SRP is correct using their personal definition and you are correct using your definition. I think your design is perfectly fine with the caveat that this event isn't a one-off whereby other similar functionality is done in different ways. Consistency is far, far, far more important to pay attention to than some vague guideline like SRP which carried to the extreme ends up with tons of very easy to understand classes that nobody knows how to make work in a system.

    – Dunk
    21 hours ago











  • Does SaveChanges update multiple users?

    – Nishant
    15 hours ago












  • 16





    Your retort is: "OK, so how would you write it so that it doesn't violate SRP but still allows a single point of modification?"

    – Robert Harvey
    yesterday






  • 29





    My observation is that raising an event does not add an additional responsibility. In fact, quite the opposite: it delegates the responsibility somewhere else.

    – Robert Harvey
    yesterday











  • I think your coworker is right, but your question is valid and useful, so upvoted!

    – Andres F.
    22 hours ago






  • 11





    There's no such thing as a definitive definition of a Single Responsibility. The person pointing out that it violates SRP is correct using their personal definition and you are correct using your definition. I think your design is perfectly fine with the caveat that this event isn't a one-off whereby other similar functionality is done in different ways. Consistency is far, far, far more important to pay attention to than some vague guideline like SRP which carried to the extreme ends up with tons of very easy to understand classes that nobody knows how to make work in a system.

    – Dunk
    21 hours ago











  • Does SaveChanges update multiple users?

    – Nishant
    15 hours ago







16




16





Your retort is: "OK, so how would you write it so that it doesn't violate SRP but still allows a single point of modification?"

– Robert Harvey
yesterday





Your retort is: "OK, so how would you write it so that it doesn't violate SRP but still allows a single point of modification?"

– Robert Harvey
yesterday




29




29





My observation is that raising an event does not add an additional responsibility. In fact, quite the opposite: it delegates the responsibility somewhere else.

– Robert Harvey
yesterday





My observation is that raising an event does not add an additional responsibility. In fact, quite the opposite: it delegates the responsibility somewhere else.

– Robert Harvey
yesterday













I think your coworker is right, but your question is valid and useful, so upvoted!

– Andres F.
22 hours ago





I think your coworker is right, but your question is valid and useful, so upvoted!

– Andres F.
22 hours ago




11




11





There's no such thing as a definitive definition of a Single Responsibility. The person pointing out that it violates SRP is correct using their personal definition and you are correct using your definition. I think your design is perfectly fine with the caveat that this event isn't a one-off whereby other similar functionality is done in different ways. Consistency is far, far, far more important to pay attention to than some vague guideline like SRP which carried to the extreme ends up with tons of very easy to understand classes that nobody knows how to make work in a system.

– Dunk
21 hours ago





There's no such thing as a definitive definition of a Single Responsibility. The person pointing out that it violates SRP is correct using their personal definition and you are correct using your definition. I think your design is perfectly fine with the caveat that this event isn't a one-off whereby other similar functionality is done in different ways. Consistency is far, far, far more important to pay attention to than some vague guideline like SRP which carried to the extreme ends up with tons of very easy to understand classes that nobody knows how to make work in a system.

– Dunk
21 hours ago













Does SaveChanges update multiple users?

– Nishant
15 hours ago





Does SaveChanges update multiple users?

– Nishant
15 hours ago










9 Answers
9






active

oldest

votes


















13














Sending a notification that the persistent data store changed seems like a sensible thing to do when saving.



Of course you shouldn't treat Add as a special case - you'd have to fire events for Modify and Delete as well. It's the special treatment of the "Add" case that smells, forces the reader to explain why it smells, and ultimately leads some readers of the code to conclude it must violate SRP.



A "notifying" repository that can be queried, changed, and fires events on changes, is a perfectly normal object. You can expect to find multiple variations thereof in pretty much any decently sized project.




But is a "notifying" repository actually what you need? You mentioned C#: Many people would agree that using a System.Collections.ObjectModel.ObservableCollection<> instead of System.Collections.Generic.List<> when the latter is all you need is all kinds of bad and wrong, but few would immediately point to SRP.



What you are doing now is swapping your UserList _userRepository with an ObservableUserCollection _userRepository. If that's the best course of action or not depends on the application. But while it unquestionably makes the _userRepository considerably less lightweight, in my humble opinion it doesn't violate SRP.






share|improve this answer

























  • The problem with using ObservableCollectionfor this case is, it triggers the equivalent event not at the call to SaveChanges, but at the call to Add, which would lead to a very different behaviour than the one shown in the example. See my answer how to keep the original repo lightweight, and still stick to the SRP by keeping the semantics intact.

    – Doc Brown
    2 hours ago












  • @DocBrown I invoked the known classes ObservableCollection<> and List<> for comparison and context. I did not mean to recommend using the actual classes for either the internal implementation or the external interface.

    – Peter
    52 mins ago


















12














Yes, it is a violation of the single responsibility principle and a valid point.



A better design would be to have a separate process retrieve 'new users' from the repository and send the emails. Keeping track of which users have been sent an email, failures, resends, etc., etc.



This way you can handle errors, crashes and the like as well as avoiding your repository grabbing every requirement which has the idea that events happen "when something is committed to the database".



The repository doesn't know that a user you add is a new user. Its responsibility is simply storing the user.



It's probably worth expanding on the comments below.




The repository doesn't know that a user you add is a new user - yes it
does, it has a method called Add. Its semantics implies that all added
users are new users. Combine all the arguments passed to Add before
calling Save - and you get all new users




Incorrect. You are conflating "Added to the Repository" and "New".



"Added to the Repository" means just what it says. I can add and remove and re-add users to various repositories.



"New" is a state of a user defined by business rules.



Currently the business rule might be "New == just been added to the repository", but that doesn't mean it's not a separate responsibility to know about and apply that rule.



You have to be careful to avoid this kind of database-centric thinking. You will have edge case processes which add non-new users to the repository and when you send emails to them all the business will say "Of course those are not 'new' users! The actual rule is X"




This answer is IMHO quite missing the point: the repo is exactly the
one central place in the code which knows when new users are added




Incorrect. For the reasons above, plus it's not a central location unless you actually include the email sending code in the class rather than just raising an event.



You will have applications which use the repository class, but don't have the code to send the email. When you add users in those applications the email will not be sent.






share|improve this answer




















  • 8





    The repository doesn't know that a user you add is a new user - yes it does, it has a method called Add. Its semantics implies that all added users are new users. Combine all the arguments passed to Add before calling Save - and you get all new users.

    – Andre Borges
    19 hours ago











  • I like this suggestion. However, pragmatism prevails over purity. Depending on the circumstances, adding an entirely new architectural layer to an existing application can be difficult to justify if all you need to do is literally send a single email when a user is added.

    – Alexander
    18 hours ago











  • But the event is not saying user added. It says user created. If we consider naming things properly and we agree with the semantical differences between add and create, then the event in the snippet is either wrong named or missplaced. I don't think the reviewer had anything against notyfing repositories. Probably It was concerned about the kind of event and its side effects.

    – Laiv
    11 hours ago






  • 3





    @Andre New to the repo, but not necessarily "new" in the business sense. its the conflation of these two ideas that's hiding the extra responsibility from first glance. I might import a tonne of old users to my new repository, or remove and re-add a user etc. There will be business rules about what a "new user" is beyond "has been added to the dB"

    – Ewan
    11 hours ago











  • @Ewan, I agree that determining business sense is not the repository's job, but my repository does not do that, event handler does. Okay, my repository does fire event called UserCreatedEvent, but to avoid confusion I could rename it to UserAddedToRepositoryEvent, and event handler could decide if a certain event is considered to be "a new user". Currently, every event would qualify, but if business rules change (and they might, as you correctly mentioned) - then I would only have to change event handler, not the repository.

    – Andre Borges
    8 hours ago


















6















Is this a valid point?




Yes it is, although it depends a lot on the structure of your code. I don't have the full context so I will try to talk in general.




It seems to me that the raising an event here is essentially the same thing as logging: just adding some side functionality to the function.




It absolutely isn't. Logging is not part of the business flow, it can be disabled, it shouldn't cause (business) side effects and should not influence the state and heath of your application in any way, even if you were for some reason not able to log anything anymore. Now compare that with the logic you added.




And SRP does not prohibit you from using logging or firing events in your functions, it just says that such logic should be encapsulated in other classes, and it is OK for a repository to call these other classes.




SRP works in tandem with ISP (S and I in SOLID). You end up with many classes and methods that do very specific things and nothing else. They are very focused, very easy to update or replace, and in general easy(er) to test. Of course in practice you'll also have a few bigger classes that deal with the orchestration: they will have a number of dependencies, and they will focus not on atomised actions, but on business actions, which may require multiple steps. As long as the business context is clear, they can too be called single responsibility, but as you correctly said, as the code grows, you may want to abstract some of it into new classes / interfaces.



Now back to your particular example. If you absolutely must send a notification whenever a user is created and maybe even perform other more specialised actions, then you could create a separate service that encapsulates this requirement, something like UserCreationService, which exposes one method, Add(user), which handles both the storage (the call to your repository) and the notification as a single business action. Or do it in your original snippet, after _userRepository.SaveChanges();






share|improve this answer


















  • 2





    Logging is not part of the business flow - how is this relevant in the context of SRP? If the purpose of my event would be to send new user data to Google Analytics - then disabling it would have the same business effect as disabling logging: not critical, but pretty upsetting. What is the rule of a thumb for adding/not adding new logic to a function? "Will disabling it cause major business side effects?"

    – Andre Borges
    19 hours ago


















4














Yes, it can be a valid requirement to have a repository which fires certain events on certain actions like Add or SaveChanges - and I am not going to question this (like some other answers), so let us assume this requirement is perfectly justified in the context of your system. And yes, encoding the event mechanics as well as the logging as well as the saving into one method violates the SRP (to a certain degree).



The solution to this is to let your original repository stay to be responsible for committing changes to database, nothing else, and make a proxy repository which has exactly the same public interface, reuses the original repository and adds the additional event mechanics to the methods:



// In EventFiringUserRepo:
void SaveChanges()

_basicRepo.SaveChanges();
FireEventsForNewlyAddedUsers();


void FireEventsForNewlyAddedUsers()

foreach (var newUser in _basicRepo.DataContext.GetAddedUsers())

_eventService.RaiseEvent(new UserCreatedEvent(newUser))




The new and the old repository class should both derive from a common interface, like shown in the classic Proxy pattern description.



Then, in your original code, initialize _userRepository by an object of the new EventFiringUserRepo class. That way, you keep the original repository separated from the event mechanics. If required, you can have the event firing repository and the original repository side-by-side and let the callers decide if they use either the former or the latter.



If this kind of separation is really worth it in the context of your system is something you and your reviewer have to decide by yourself. I probably would not separate the logging from the original code using the same approach, though it would be possible in principle.






share|improve this answer




















  • 2





    In addition to this answer. There are alternatives to proxies, like AOP.

    – Laiv
    10 hours ago







  • 1





    I think you miss the point, its not that raising an event breaks the SRP its that raising an event only for "New" users requires the repo to be responsible for knowing what constitutes a "New" user rather than a "Newly Added to Me" user

    – Ewan
    9 hours ago











  • @Ewan: please read the question again. It was about a place in code which does certain actions which needs to be coupled with other actions outside of the responsivility of that object. And putting the action and the event raising in one place was questioned by a peer reviewer as breaking the SRP. The example of "saving new users" is only for the purpose of demonstration, call the example contrived if you like, but that is IMHO not the point of the question.

    – Doc Brown
    4 hours ago






  • 1





    This is the best/correct answer IMO. Not only does it maintain SRP, but it also maintains the Open/Closed principle. And think of all of the automated tests that changes within the class could break. Modifying existing tests when new functionality is added is a big smell.

    – Keith Payne
    3 hours ago












  • This is an interesting answer, and a good illustration of the OCP. But, let's extrapolate. In a week, they want to add a "add this user to the you get a month free tier", and, after that month, add "don't forget to charge their credit card since they forgot to unsubscribe". Do you suggest "proxies all the way down"? (This is where I fear the OCP starts to fall apart)

    – user949300
    30 mins ago



















4














SRP is, theoretically, about people. Uncle Bob Link added Here (Thanks Robert Harvey for providing it in your comment).



The correct question is:



Which "stakeholder" added the "send emails" requirement?



If that stakeholder is also in charge of data persistence (unlikely but possible) then this does not violate SRP. Otherwise, it does.






share|improve this answer




















  • 1





    Interesting - I never heard of this interpretation of the SRP. Do you have any pointers to more information / literature about this interpretation?

    – sleske
    4 hours ago






  • 1





    @sleske: From Uncle Bob himself: "And this gets to the crux of the Single Responsibility Principle. This principle is about people. When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function."

    – Robert Harvey
    1 hour ago












  • Thanks Robert. IMO, The name "Single Responsibility Principle" is terrible, as it sounds simple, but too few people follow up on what is the intended meaning of "responsibility". Kind of like how OOP has mutated from many of it's original concepts, and is now a fairly meaningless term.

    – user949300
    50 mins ago






  • 1





    Yep. That's what happened to the term REST. Even Roy Fielding says people are using it wrong.

    – Robert Harvey
    46 mins ago


















1














While technically there's nothing wrong with repositories notifying events, I would suggest looking at it from a functional point of view where its convenience rises some concerns.




Creating users is one thing, its persistence a different one.




Premise of mine



Consider the previous premise before deciding whether the repository is the proper place to notify business events (regardless of the SRP). Note that I said business event because to me UserCreated has a different connotation than UserStored or UserAdded 1. I would also consider each of those events to be addressed to different audiences.



On one side, creating users is a business-specific rule that might or might not involve persistence. It might involve more business operations, involving more database/network operations. Operations the persistence layer is unaware of. The persistence layer doesn't have enough context to decide whether the use case ended successfully or not.



On the flip side, it's not necessarily true that _dataContext.SaveChanges(); has persisted the user successfully. It will depend on the database's transaction span. For instance, it could be true for databases like MongoDB, which transactions are atomic, but it could not, for traditional RDBMS implementing ACID transactions where there could be more transactions involved and yet to be committed.




Is this a valid point?




I would dare to say that, It's not only a matter of SRP (technically speaking), It's also a matter of convenience (functionally speaking). Is it convenient to cast business events from components that are not totally aware of the business operations in progress? Do they represent the right place as much as the right moment? Should the persistence orchestrate my business through notifications like this? Can we invalidate premature events?* 2




It seems to me that the raising an event here is essentially the same
thing as logging




Absolutely not. Logging is meant to have no side effects, however, I suspect your event UserCreated is likely to cause other business operations to happen. Like notifications. 3




it just says that such logic should be encapsulated in other classes,
and it is OK for a repository to call these other classes




Not necessarily true. SRP is not a class-specific concern only. It operates at different levels of abstractions, like layers, libraries and systems! It's about cohesion, about keeping together what changes for the same reasons. If the user creation (use case) changes it's likely the moment and the reasons for the event to happen also changes.




1: Naming things adequately also matters.



2: Say we sent UserCreated after _dataContext.SaveChanges();, but the whole database transaction failed later due to connection issues or constraints violations. Be careful with premature broadcasting of events, because its side effects can be hard to undo (if that is even possible).



3: Bear in mind that a sent email cannot be undon






share|improve this answer

























  • +1 Very good point about the transaction span. It can be premature to assert the user has been created, because rollbacks can happen; and unlike with a log, it's likely some other part of the app does something with the event.

    – Andres F.
    22 hours ago






  • 1





    Exactly. Events denote certainity. Something happened but it's over.

    – Laiv
    22 hours ago












  • @Laiv: Except when they don't. Microsoft has all sorts of events prefixed with Before or Preview that make no guarantees at all about certainty.

    – Robert Harvey
    22 hours ago











  • Hmm. I have never seen this sort of events. Would you mind sharing some references?

    – Laiv
    21 hours ago






  • 1





    @RobertHarvey No, my answer is, "Learn to think like Python developers think and then apply those lessons in C#." Sadly, this is not a common approach, even though I can personally attest to the improvement to my own C# code doing it. (That is true in spite of the ecosystem, and I find other devs that I've worked with also appreciated the approaches I used and suggested.)

    – jpmc26
    1 hour ago



















1














Don't worry about single responsibility principle. It's not going to help you make a good decision here because you can subjectively choose a particular concept as a "responsibility." You could say the class' responsibility is managing data persistence to the database, or you could say its responsibility is to perform all the work related to creating a user. These are just different levels of the application's behavior, and they're both valid conceptual expressions of a "single responsibility." So this principle is unhelpful for solving your problem.



The most useful principle to apply in this case is the principle of least surprise. So let's ask the question: is it surprising that a repository with the primary role of persisting data to a database also sends e-mails?



Yes, it very much is surprising. These are two completely separate external systems, and the name SaveChanges does not imply also sending notifications. The fact you delegate this out to an event makes the behavior even more surprising, since someone reading the code can no longer easily see what additional behaviors are invoked. Indirection harms readability. Sometimes, the benefits are worth the readability costs, but not when you're automatically invoking an additional external system that has effects observable to end users. (Logging can be excluded here since its effect is essentially record keeping for debugging purposes. End users do not consume the log, so there is no harm in always logging.) Even worse, this reduces flexibility in the timing of sending the e-mail, making it impossible to interleave other operations between the save and the notification.



If your code typically needs to send a notification when a user is successfully created, you could create a method that does so:



public void AddUserAndNotify(IUserRepository repo, IEmailNotification notifier, MyUser user)

repo.Add(user);
repo.SaveChanges();
notifier.SendUserCreatedNotification(user);



But whether this adds value depends on your application's specifics.




I'd actually discourage the existence of the SaveChanges method at all. This method will presumably commit a database transaction, but other repositories might have modified the database in the same transaction. The fact it commits all of them is again surprising, since SaveChanges is specifically tied to this instance of the user repository.



The most straightforward pattern for managing a database transaction is an outer using block:



using (DataContext context = new DataContext())

_userRepository.Add(context, user);
context.SaveChanges();
notifier.SendUserCreatedNotification(user);



This gives the programmer explicit control over when changes for all repositories are saved, forces the code to explicitly document the sequence of events that must occur before a commit, ensures a rollback is issued on error (assuming that DataContext.Dispose issues a rollback), and avoids hidden connections between stateful classes.



I'd also prefer not to send the e-mail directly in the request. It would be more robust to record the need for a notification in a queue. This would allow for better failure handling. In particular, if an error occurs sending the e-mail, it can be tried again later without interrupting saving the user, and it avoids the case where the user is created but an error is returned by the site.



using (DataContext context = new DataContext())

_userRepository.Add(context, user);
_emailNotificationQueue.AddUserCreateNotification(user);
_emailNotificationQueue.Commit();
context.SaveChanges();



It's better to commit the notification queue first since the queue's consumer can verify that the user exists before sending the e-mail, in the event that the context.SaveChanges() call fails. (Otherwise, you'll need a full blown two-phase commit strategy to avoid heisenbugs.)




The bottom line is to be practical. Actually think through the consequences (both in terms of risk and benefit) of writing code a particular way. I find that "single responsibility principle" doesn't very often help me do that, while "principle of least surprise" often helps me get into another developer's head (so to speak) and think about what might happen.






share|improve this answer




















  • 1





    is it surprising that a repository with the primary role of persisting data to a database also sends e-mails - I think you missed the point of my question. My repository is not sending emails. It just raises an event, and how this event is handled - is the responsibility of external code.

    – Andre Borges
    1 hour ago






  • 1





    @AndreBorges My answer says, "The fact you delegate this out to an event makes the behavior even more surprising, since someone reading the code can no longer easily see what additional behaviors are invoked. Indirection harms readability." You're still thinking in terms of SRP. The whole point of my answer is that thinking that way is not helping you write better code. Good code makes most of what someone else needs to know about it obvious when they read it, and the way you've structured your code does not do so.

    – jpmc26
    1 hour ago












  • You're basically making the argument "don't use events."

    – Robert Harvey
    1 hour ago











  • @RobertHarvey Yup. They make the connections between different parts of code much harder to discover, which makes understanding the sequence of events that occurs a much more difficult task. As I said, sometimes it's worth that price, but it's definitely a price you want to avoid paying whenever you can (which is usually in my experience).

    – jpmc26
    1 hour ago







  • 1





    @RobertHarvey thinks Actually, I take it back. I'm not arguing against events. What I'm arguing against in this answer is indirection that is unnecessary. All events do in this case is get your code out of sequence. The repository is not waiting for something to happen; it's performing an operation on request. Events should be limited to triggering behavior that occurs at a time under the control of an external source (such as a user). A web request handler is essentially sequential: once the request is received, perform operations in order until a response is generated and returned.

    – jpmc26
    34 mins ago



















0














Currently SaveChanges does two things: it saves the changes and logs that it does so. Now you want to add another thing to it: send email notifications.



You had the clever idea to add an event to it, but this was criticised for violating the Single Responsibility Principle (SRP), without noticing that it had already been violated.



To get a pure SRP solution, first trigger the event, then call all the hooks for that event, of which there are now three: saving, logging, and finally sending emails.



Either you trigger the event first, or you have to add to SaveChanges. Your solution is a hybrid between the two. It doesn't address the existing violation while it does encourage preventing it from increasing beyond three things. Refactoring the existing code to comply with SRP might require more work than is strictly necessary. It's up to your project how far they want to take SRP.






share|improve this answer






























    0














    The code already violated the SRP -- the same class was responsible for communicating with the data context and logging.



    You just upgrade it to having 3 responsibilities.



    One way to strip things back to 1 responsibility would be to abstract the _userRepository; make it a command-broadcaster.



    It has a set of commands, plus a set of listeners. It gets commands, and broadcasts them to its listeners. Possibly those listeners are ordered, and maybe they can even say the command failed (which in turn is broadcast to listeners who had already been notified).



    Now, most of the commands may have only 1 listener (the data context). SaveChanges, prior to your changes, has 2 -- the data context, and then the logger.



    Your change then adds another listener to save changes, which is to raise new user created events in the event service.



    There are a few benefits to this. You can now remove, upgrade, or replicate the logging code without the rest of your code caring. You can add more triggers at the save changes for more things that need it.



    All of this gets decided when the _userRepository is created and wired up (or, maybe, those extra features get added/removed on the fly; being able to add/enhance logging while the application runs could be of use).






    share|improve this answer






















      Your Answer








      StackExchange.ready(function()
      var channelOptions =
      tags: "".split(" "),
      id: "131"
      ;
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function()
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled)
      StackExchange.using("snippets", function()
      createEditor();
      );

      else
      createEditor();

      );

      function createEditor()
      StackExchange.prepareEditor(
      heartbeatType: 'answer',
      autoActivateHeartbeat: false,
      convertImagesToLinks: false,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: null,
      bindNavPrevention: true,
      postfix: "",
      imageUploader:
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      ,
      onDemand: false,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      );



      );













      draft saved

      draft discarded


















      StackExchange.ready(
      function ()
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fsoftwareengineering.stackexchange.com%2fquestions%2f389237%2fapplicability-of-single-responsibility-principle%23new-answer', 'question_page');

      );

      Post as a guest















      Required, but never shown




















      StackExchange.ready(function ()
      $("#show-editor-button input, #show-editor-button button").click(function ()
      var showEditor = function()
      $("#show-editor-button").hide();
      $("#post-form").removeClass("dno");
      StackExchange.editor.finallyInit();
      ;

      var useFancy = $(this).data('confirm-use-fancy');
      if(useFancy == 'True')
      var popupTitle = $(this).data('confirm-fancy-title');
      var popupBody = $(this).data('confirm-fancy-body');
      var popupAccept = $(this).data('confirm-fancy-accept-button');

      $(this).loadPopup(
      url: '/post/self-answer-popup',
      loaded: function(popup)
      var pTitle = $(popup).find('h2');
      var pBody = $(popup).find('.popup-body');
      var pSubmit = $(popup).find('.popup-submit');

      pTitle.text(popupTitle);
      pBody.html(popupBody);
      pSubmit.val(popupAccept).click(showEditor);

      )
      else
      var confirmText = $(this).data('confirm-text');
      if (confirmText ? confirm(confirmText) : true)
      showEditor();


      );
      );






      9 Answers
      9






      active

      oldest

      votes








      9 Answers
      9






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      13














      Sending a notification that the persistent data store changed seems like a sensible thing to do when saving.



      Of course you shouldn't treat Add as a special case - you'd have to fire events for Modify and Delete as well. It's the special treatment of the "Add" case that smells, forces the reader to explain why it smells, and ultimately leads some readers of the code to conclude it must violate SRP.



      A "notifying" repository that can be queried, changed, and fires events on changes, is a perfectly normal object. You can expect to find multiple variations thereof in pretty much any decently sized project.




      But is a "notifying" repository actually what you need? You mentioned C#: Many people would agree that using a System.Collections.ObjectModel.ObservableCollection<> instead of System.Collections.Generic.List<> when the latter is all you need is all kinds of bad and wrong, but few would immediately point to SRP.



      What you are doing now is swapping your UserList _userRepository with an ObservableUserCollection _userRepository. If that's the best course of action or not depends on the application. But while it unquestionably makes the _userRepository considerably less lightweight, in my humble opinion it doesn't violate SRP.






      share|improve this answer

























      • The problem with using ObservableCollectionfor this case is, it triggers the equivalent event not at the call to SaveChanges, but at the call to Add, which would lead to a very different behaviour than the one shown in the example. See my answer how to keep the original repo lightweight, and still stick to the SRP by keeping the semantics intact.

        – Doc Brown
        2 hours ago












      • @DocBrown I invoked the known classes ObservableCollection<> and List<> for comparison and context. I did not mean to recommend using the actual classes for either the internal implementation or the external interface.

        – Peter
        52 mins ago















      13














      Sending a notification that the persistent data store changed seems like a sensible thing to do when saving.



      Of course you shouldn't treat Add as a special case - you'd have to fire events for Modify and Delete as well. It's the special treatment of the "Add" case that smells, forces the reader to explain why it smells, and ultimately leads some readers of the code to conclude it must violate SRP.



      A "notifying" repository that can be queried, changed, and fires events on changes, is a perfectly normal object. You can expect to find multiple variations thereof in pretty much any decently sized project.




      But is a "notifying" repository actually what you need? You mentioned C#: Many people would agree that using a System.Collections.ObjectModel.ObservableCollection<> instead of System.Collections.Generic.List<> when the latter is all you need is all kinds of bad and wrong, but few would immediately point to SRP.



      What you are doing now is swapping your UserList _userRepository with an ObservableUserCollection _userRepository. If that's the best course of action or not depends on the application. But while it unquestionably makes the _userRepository considerably less lightweight, in my humble opinion it doesn't violate SRP.






      share|improve this answer

























      • The problem with using ObservableCollectionfor this case is, it triggers the equivalent event not at the call to SaveChanges, but at the call to Add, which would lead to a very different behaviour than the one shown in the example. See my answer how to keep the original repo lightweight, and still stick to the SRP by keeping the semantics intact.

        – Doc Brown
        2 hours ago












      • @DocBrown I invoked the known classes ObservableCollection<> and List<> for comparison and context. I did not mean to recommend using the actual classes for either the internal implementation or the external interface.

        – Peter
        52 mins ago













      13












      13








      13







      Sending a notification that the persistent data store changed seems like a sensible thing to do when saving.



      Of course you shouldn't treat Add as a special case - you'd have to fire events for Modify and Delete as well. It's the special treatment of the "Add" case that smells, forces the reader to explain why it smells, and ultimately leads some readers of the code to conclude it must violate SRP.



      A "notifying" repository that can be queried, changed, and fires events on changes, is a perfectly normal object. You can expect to find multiple variations thereof in pretty much any decently sized project.




      But is a "notifying" repository actually what you need? You mentioned C#: Many people would agree that using a System.Collections.ObjectModel.ObservableCollection<> instead of System.Collections.Generic.List<> when the latter is all you need is all kinds of bad and wrong, but few would immediately point to SRP.



      What you are doing now is swapping your UserList _userRepository with an ObservableUserCollection _userRepository. If that's the best course of action or not depends on the application. But while it unquestionably makes the _userRepository considerably less lightweight, in my humble opinion it doesn't violate SRP.






      share|improve this answer















      Sending a notification that the persistent data store changed seems like a sensible thing to do when saving.



      Of course you shouldn't treat Add as a special case - you'd have to fire events for Modify and Delete as well. It's the special treatment of the "Add" case that smells, forces the reader to explain why it smells, and ultimately leads some readers of the code to conclude it must violate SRP.



      A "notifying" repository that can be queried, changed, and fires events on changes, is a perfectly normal object. You can expect to find multiple variations thereof in pretty much any decently sized project.




      But is a "notifying" repository actually what you need? You mentioned C#: Many people would agree that using a System.Collections.ObjectModel.ObservableCollection<> instead of System.Collections.Generic.List<> when the latter is all you need is all kinds of bad and wrong, but few would immediately point to SRP.



      What you are doing now is swapping your UserList _userRepository with an ObservableUserCollection _userRepository. If that's the best course of action or not depends on the application. But while it unquestionably makes the _userRepository considerably less lightweight, in my humble opinion it doesn't violate SRP.







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited 17 hours ago

























      answered 19 hours ago









      PeterPeter

      2,995516




      2,995516












      • The problem with using ObservableCollectionfor this case is, it triggers the equivalent event not at the call to SaveChanges, but at the call to Add, which would lead to a very different behaviour than the one shown in the example. See my answer how to keep the original repo lightweight, and still stick to the SRP by keeping the semantics intact.

        – Doc Brown
        2 hours ago












      • @DocBrown I invoked the known classes ObservableCollection<> and List<> for comparison and context. I did not mean to recommend using the actual classes for either the internal implementation or the external interface.

        – Peter
        52 mins ago

















      • The problem with using ObservableCollectionfor this case is, it triggers the equivalent event not at the call to SaveChanges, but at the call to Add, which would lead to a very different behaviour than the one shown in the example. See my answer how to keep the original repo lightweight, and still stick to the SRP by keeping the semantics intact.

        – Doc Brown
        2 hours ago












      • @DocBrown I invoked the known classes ObservableCollection<> and List<> for comparison and context. I did not mean to recommend using the actual classes for either the internal implementation or the external interface.

        – Peter
        52 mins ago
















      The problem with using ObservableCollectionfor this case is, it triggers the equivalent event not at the call to SaveChanges, but at the call to Add, which would lead to a very different behaviour than the one shown in the example. See my answer how to keep the original repo lightweight, and still stick to the SRP by keeping the semantics intact.

      – Doc Brown
      2 hours ago






      The problem with using ObservableCollectionfor this case is, it triggers the equivalent event not at the call to SaveChanges, but at the call to Add, which would lead to a very different behaviour than the one shown in the example. See my answer how to keep the original repo lightweight, and still stick to the SRP by keeping the semantics intact.

      – Doc Brown
      2 hours ago














      @DocBrown I invoked the known classes ObservableCollection<> and List<> for comparison and context. I did not mean to recommend using the actual classes for either the internal implementation or the external interface.

      – Peter
      52 mins ago





      @DocBrown I invoked the known classes ObservableCollection<> and List<> for comparison and context. I did not mean to recommend using the actual classes for either the internal implementation or the external interface.

      – Peter
      52 mins ago













      12














      Yes, it is a violation of the single responsibility principle and a valid point.



      A better design would be to have a separate process retrieve 'new users' from the repository and send the emails. Keeping track of which users have been sent an email, failures, resends, etc., etc.



      This way you can handle errors, crashes and the like as well as avoiding your repository grabbing every requirement which has the idea that events happen "when something is committed to the database".



      The repository doesn't know that a user you add is a new user. Its responsibility is simply storing the user.



      It's probably worth expanding on the comments below.




      The repository doesn't know that a user you add is a new user - yes it
      does, it has a method called Add. Its semantics implies that all added
      users are new users. Combine all the arguments passed to Add before
      calling Save - and you get all new users




      Incorrect. You are conflating "Added to the Repository" and "New".



      "Added to the Repository" means just what it says. I can add and remove and re-add users to various repositories.



      "New" is a state of a user defined by business rules.



      Currently the business rule might be "New == just been added to the repository", but that doesn't mean it's not a separate responsibility to know about and apply that rule.



      You have to be careful to avoid this kind of database-centric thinking. You will have edge case processes which add non-new users to the repository and when you send emails to them all the business will say "Of course those are not 'new' users! The actual rule is X"




      This answer is IMHO quite missing the point: the repo is exactly the
      one central place in the code which knows when new users are added




      Incorrect. For the reasons above, plus it's not a central location unless you actually include the email sending code in the class rather than just raising an event.



      You will have applications which use the repository class, but don't have the code to send the email. When you add users in those applications the email will not be sent.






      share|improve this answer




















      • 8





        The repository doesn't know that a user you add is a new user - yes it does, it has a method called Add. Its semantics implies that all added users are new users. Combine all the arguments passed to Add before calling Save - and you get all new users.

        – Andre Borges
        19 hours ago











      • I like this suggestion. However, pragmatism prevails over purity. Depending on the circumstances, adding an entirely new architectural layer to an existing application can be difficult to justify if all you need to do is literally send a single email when a user is added.

        – Alexander
        18 hours ago











      • But the event is not saying user added. It says user created. If we consider naming things properly and we agree with the semantical differences between add and create, then the event in the snippet is either wrong named or missplaced. I don't think the reviewer had anything against notyfing repositories. Probably It was concerned about the kind of event and its side effects.

        – Laiv
        11 hours ago






      • 3





        @Andre New to the repo, but not necessarily "new" in the business sense. its the conflation of these two ideas that's hiding the extra responsibility from first glance. I might import a tonne of old users to my new repository, or remove and re-add a user etc. There will be business rules about what a "new user" is beyond "has been added to the dB"

        – Ewan
        11 hours ago











      • @Ewan, I agree that determining business sense is not the repository's job, but my repository does not do that, event handler does. Okay, my repository does fire event called UserCreatedEvent, but to avoid confusion I could rename it to UserAddedToRepositoryEvent, and event handler could decide if a certain event is considered to be "a new user". Currently, every event would qualify, but if business rules change (and they might, as you correctly mentioned) - then I would only have to change event handler, not the repository.

        – Andre Borges
        8 hours ago















      12














      Yes, it is a violation of the single responsibility principle and a valid point.



      A better design would be to have a separate process retrieve 'new users' from the repository and send the emails. Keeping track of which users have been sent an email, failures, resends, etc., etc.



      This way you can handle errors, crashes and the like as well as avoiding your repository grabbing every requirement which has the idea that events happen "when something is committed to the database".



      The repository doesn't know that a user you add is a new user. Its responsibility is simply storing the user.



      It's probably worth expanding on the comments below.




      The repository doesn't know that a user you add is a new user - yes it
      does, it has a method called Add. Its semantics implies that all added
      users are new users. Combine all the arguments passed to Add before
      calling Save - and you get all new users




      Incorrect. You are conflating "Added to the Repository" and "New".



      "Added to the Repository" means just what it says. I can add and remove and re-add users to various repositories.



      "New" is a state of a user defined by business rules.



      Currently the business rule might be "New == just been added to the repository", but that doesn't mean it's not a separate responsibility to know about and apply that rule.



      You have to be careful to avoid this kind of database-centric thinking. You will have edge case processes which add non-new users to the repository and when you send emails to them all the business will say "Of course those are not 'new' users! The actual rule is X"




      This answer is IMHO quite missing the point: the repo is exactly the
      one central place in the code which knows when new users are added




      Incorrect. For the reasons above, plus it's not a central location unless you actually include the email sending code in the class rather than just raising an event.



      You will have applications which use the repository class, but don't have the code to send the email. When you add users in those applications the email will not be sent.






      share|improve this answer




















      • 8





        The repository doesn't know that a user you add is a new user - yes it does, it has a method called Add. Its semantics implies that all added users are new users. Combine all the arguments passed to Add before calling Save - and you get all new users.

        – Andre Borges
        19 hours ago











      • I like this suggestion. However, pragmatism prevails over purity. Depending on the circumstances, adding an entirely new architectural layer to an existing application can be difficult to justify if all you need to do is literally send a single email when a user is added.

        – Alexander
        18 hours ago











      • But the event is not saying user added. It says user created. If we consider naming things properly and we agree with the semantical differences between add and create, then the event in the snippet is either wrong named or missplaced. I don't think the reviewer had anything against notyfing repositories. Probably It was concerned about the kind of event and its side effects.

        – Laiv
        11 hours ago






      • 3





        @Andre New to the repo, but not necessarily "new" in the business sense. its the conflation of these two ideas that's hiding the extra responsibility from first glance. I might import a tonne of old users to my new repository, or remove and re-add a user etc. There will be business rules about what a "new user" is beyond "has been added to the dB"

        – Ewan
        11 hours ago











      • @Ewan, I agree that determining business sense is not the repository's job, but my repository does not do that, event handler does. Okay, my repository does fire event called UserCreatedEvent, but to avoid confusion I could rename it to UserAddedToRepositoryEvent, and event handler could decide if a certain event is considered to be "a new user". Currently, every event would qualify, but if business rules change (and they might, as you correctly mentioned) - then I would only have to change event handler, not the repository.

        – Andre Borges
        8 hours ago













      12












      12








      12







      Yes, it is a violation of the single responsibility principle and a valid point.



      A better design would be to have a separate process retrieve 'new users' from the repository and send the emails. Keeping track of which users have been sent an email, failures, resends, etc., etc.



      This way you can handle errors, crashes and the like as well as avoiding your repository grabbing every requirement which has the idea that events happen "when something is committed to the database".



      The repository doesn't know that a user you add is a new user. Its responsibility is simply storing the user.



      It's probably worth expanding on the comments below.




      The repository doesn't know that a user you add is a new user - yes it
      does, it has a method called Add. Its semantics implies that all added
      users are new users. Combine all the arguments passed to Add before
      calling Save - and you get all new users




      Incorrect. You are conflating "Added to the Repository" and "New".



      "Added to the Repository" means just what it says. I can add and remove and re-add users to various repositories.



      "New" is a state of a user defined by business rules.



      Currently the business rule might be "New == just been added to the repository", but that doesn't mean it's not a separate responsibility to know about and apply that rule.



      You have to be careful to avoid this kind of database-centric thinking. You will have edge case processes which add non-new users to the repository and when you send emails to them all the business will say "Of course those are not 'new' users! The actual rule is X"




      This answer is IMHO quite missing the point: the repo is exactly the
      one central place in the code which knows when new users are added




      Incorrect. For the reasons above, plus it's not a central location unless you actually include the email sending code in the class rather than just raising an event.



      You will have applications which use the repository class, but don't have the code to send the email. When you add users in those applications the email will not be sent.






      share|improve this answer















      Yes, it is a violation of the single responsibility principle and a valid point.



      A better design would be to have a separate process retrieve 'new users' from the repository and send the emails. Keeping track of which users have been sent an email, failures, resends, etc., etc.



      This way you can handle errors, crashes and the like as well as avoiding your repository grabbing every requirement which has the idea that events happen "when something is committed to the database".



      The repository doesn't know that a user you add is a new user. Its responsibility is simply storing the user.



      It's probably worth expanding on the comments below.




      The repository doesn't know that a user you add is a new user - yes it
      does, it has a method called Add. Its semantics implies that all added
      users are new users. Combine all the arguments passed to Add before
      calling Save - and you get all new users




      Incorrect. You are conflating "Added to the Repository" and "New".



      "Added to the Repository" means just what it says. I can add and remove and re-add users to various repositories.



      "New" is a state of a user defined by business rules.



      Currently the business rule might be "New == just been added to the repository", but that doesn't mean it's not a separate responsibility to know about and apply that rule.



      You have to be careful to avoid this kind of database-centric thinking. You will have edge case processes which add non-new users to the repository and when you send emails to them all the business will say "Of course those are not 'new' users! The actual rule is X"




      This answer is IMHO quite missing the point: the repo is exactly the
      one central place in the code which knows when new users are added




      Incorrect. For the reasons above, plus it's not a central location unless you actually include the email sending code in the class rather than just raising an event.



      You will have applications which use the repository class, but don't have the code to send the email. When you add users in those applications the email will not be sent.







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited 47 mins ago









      Robert Harvey

      166k42384598




      166k42384598










      answered yesterday









      EwanEwan

      42.4k33594




      42.4k33594







      • 8





        The repository doesn't know that a user you add is a new user - yes it does, it has a method called Add. Its semantics implies that all added users are new users. Combine all the arguments passed to Add before calling Save - and you get all new users.

        – Andre Borges
        19 hours ago











      • I like this suggestion. However, pragmatism prevails over purity. Depending on the circumstances, adding an entirely new architectural layer to an existing application can be difficult to justify if all you need to do is literally send a single email when a user is added.

        – Alexander
        18 hours ago











      • But the event is not saying user added. It says user created. If we consider naming things properly and we agree with the semantical differences between add and create, then the event in the snippet is either wrong named or missplaced. I don't think the reviewer had anything against notyfing repositories. Probably It was concerned about the kind of event and its side effects.

        – Laiv
        11 hours ago






      • 3





        @Andre New to the repo, but not necessarily "new" in the business sense. its the conflation of these two ideas that's hiding the extra responsibility from first glance. I might import a tonne of old users to my new repository, or remove and re-add a user etc. There will be business rules about what a "new user" is beyond "has been added to the dB"

        – Ewan
        11 hours ago











      • @Ewan, I agree that determining business sense is not the repository's job, but my repository does not do that, event handler does. Okay, my repository does fire event called UserCreatedEvent, but to avoid confusion I could rename it to UserAddedToRepositoryEvent, and event handler could decide if a certain event is considered to be "a new user". Currently, every event would qualify, but if business rules change (and they might, as you correctly mentioned) - then I would only have to change event handler, not the repository.

        – Andre Borges
        8 hours ago












      • 8





        The repository doesn't know that a user you add is a new user - yes it does, it has a method called Add. Its semantics implies that all added users are new users. Combine all the arguments passed to Add before calling Save - and you get all new users.

        – Andre Borges
        19 hours ago











      • I like this suggestion. However, pragmatism prevails over purity. Depending on the circumstances, adding an entirely new architectural layer to an existing application can be difficult to justify if all you need to do is literally send a single email when a user is added.

        – Alexander
        18 hours ago











      • But the event is not saying user added. It says user created. If we consider naming things properly and we agree with the semantical differences between add and create, then the event in the snippet is either wrong named or missplaced. I don't think the reviewer had anything against notyfing repositories. Probably It was concerned about the kind of event and its side effects.

        – Laiv
        11 hours ago






      • 3





        @Andre New to the repo, but not necessarily "new" in the business sense. its the conflation of these two ideas that's hiding the extra responsibility from first glance. I might import a tonne of old users to my new repository, or remove and re-add a user etc. There will be business rules about what a "new user" is beyond "has been added to the dB"

        – Ewan
        11 hours ago











      • @Ewan, I agree that determining business sense is not the repository's job, but my repository does not do that, event handler does. Okay, my repository does fire event called UserCreatedEvent, but to avoid confusion I could rename it to UserAddedToRepositoryEvent, and event handler could decide if a certain event is considered to be "a new user". Currently, every event would qualify, but if business rules change (and they might, as you correctly mentioned) - then I would only have to change event handler, not the repository.

        – Andre Borges
        8 hours ago







      8




      8





      The repository doesn't know that a user you add is a new user - yes it does, it has a method called Add. Its semantics implies that all added users are new users. Combine all the arguments passed to Add before calling Save - and you get all new users.

      – Andre Borges
      19 hours ago





      The repository doesn't know that a user you add is a new user - yes it does, it has a method called Add. Its semantics implies that all added users are new users. Combine all the arguments passed to Add before calling Save - and you get all new users.

      – Andre Borges
      19 hours ago













      I like this suggestion. However, pragmatism prevails over purity. Depending on the circumstances, adding an entirely new architectural layer to an existing application can be difficult to justify if all you need to do is literally send a single email when a user is added.

      – Alexander
      18 hours ago





      I like this suggestion. However, pragmatism prevails over purity. Depending on the circumstances, adding an entirely new architectural layer to an existing application can be difficult to justify if all you need to do is literally send a single email when a user is added.

      – Alexander
      18 hours ago













      But the event is not saying user added. It says user created. If we consider naming things properly and we agree with the semantical differences between add and create, then the event in the snippet is either wrong named or missplaced. I don't think the reviewer had anything against notyfing repositories. Probably It was concerned about the kind of event and its side effects.

      – Laiv
      11 hours ago





      But the event is not saying user added. It says user created. If we consider naming things properly and we agree with the semantical differences between add and create, then the event in the snippet is either wrong named or missplaced. I don't think the reviewer had anything against notyfing repositories. Probably It was concerned about the kind of event and its side effects.

      – Laiv
      11 hours ago




      3




      3





      @Andre New to the repo, but not necessarily "new" in the business sense. its the conflation of these two ideas that's hiding the extra responsibility from first glance. I might import a tonne of old users to my new repository, or remove and re-add a user etc. There will be business rules about what a "new user" is beyond "has been added to the dB"

      – Ewan
      11 hours ago





      @Andre New to the repo, but not necessarily "new" in the business sense. its the conflation of these two ideas that's hiding the extra responsibility from first glance. I might import a tonne of old users to my new repository, or remove and re-add a user etc. There will be business rules about what a "new user" is beyond "has been added to the dB"

      – Ewan
      11 hours ago













      @Ewan, I agree that determining business sense is not the repository's job, but my repository does not do that, event handler does. Okay, my repository does fire event called UserCreatedEvent, but to avoid confusion I could rename it to UserAddedToRepositoryEvent, and event handler could decide if a certain event is considered to be "a new user". Currently, every event would qualify, but if business rules change (and they might, as you correctly mentioned) - then I would only have to change event handler, not the repository.

      – Andre Borges
      8 hours ago





      @Ewan, I agree that determining business sense is not the repository's job, but my repository does not do that, event handler does. Okay, my repository does fire event called UserCreatedEvent, but to avoid confusion I could rename it to UserAddedToRepositoryEvent, and event handler could decide if a certain event is considered to be "a new user". Currently, every event would qualify, but if business rules change (and they might, as you correctly mentioned) - then I would only have to change event handler, not the repository.

      – Andre Borges
      8 hours ago











      6















      Is this a valid point?




      Yes it is, although it depends a lot on the structure of your code. I don't have the full context so I will try to talk in general.




      It seems to me that the raising an event here is essentially the same thing as logging: just adding some side functionality to the function.




      It absolutely isn't. Logging is not part of the business flow, it can be disabled, it shouldn't cause (business) side effects and should not influence the state and heath of your application in any way, even if you were for some reason not able to log anything anymore. Now compare that with the logic you added.




      And SRP does not prohibit you from using logging or firing events in your functions, it just says that such logic should be encapsulated in other classes, and it is OK for a repository to call these other classes.




      SRP works in tandem with ISP (S and I in SOLID). You end up with many classes and methods that do very specific things and nothing else. They are very focused, very easy to update or replace, and in general easy(er) to test. Of course in practice you'll also have a few bigger classes that deal with the orchestration: they will have a number of dependencies, and they will focus not on atomised actions, but on business actions, which may require multiple steps. As long as the business context is clear, they can too be called single responsibility, but as you correctly said, as the code grows, you may want to abstract some of it into new classes / interfaces.



      Now back to your particular example. If you absolutely must send a notification whenever a user is created and maybe even perform other more specialised actions, then you could create a separate service that encapsulates this requirement, something like UserCreationService, which exposes one method, Add(user), which handles both the storage (the call to your repository) and the notification as a single business action. Or do it in your original snippet, after _userRepository.SaveChanges();






      share|improve this answer


















      • 2





        Logging is not part of the business flow - how is this relevant in the context of SRP? If the purpose of my event would be to send new user data to Google Analytics - then disabling it would have the same business effect as disabling logging: not critical, but pretty upsetting. What is the rule of a thumb for adding/not adding new logic to a function? "Will disabling it cause major business side effects?"

        – Andre Borges
        19 hours ago















      6















      Is this a valid point?




      Yes it is, although it depends a lot on the structure of your code. I don't have the full context so I will try to talk in general.




      It seems to me that the raising an event here is essentially the same thing as logging: just adding some side functionality to the function.




      It absolutely isn't. Logging is not part of the business flow, it can be disabled, it shouldn't cause (business) side effects and should not influence the state and heath of your application in any way, even if you were for some reason not able to log anything anymore. Now compare that with the logic you added.




      And SRP does not prohibit you from using logging or firing events in your functions, it just says that such logic should be encapsulated in other classes, and it is OK for a repository to call these other classes.




      SRP works in tandem with ISP (S and I in SOLID). You end up with many classes and methods that do very specific things and nothing else. They are very focused, very easy to update or replace, and in general easy(er) to test. Of course in practice you'll also have a few bigger classes that deal with the orchestration: they will have a number of dependencies, and they will focus not on atomised actions, but on business actions, which may require multiple steps. As long as the business context is clear, they can too be called single responsibility, but as you correctly said, as the code grows, you may want to abstract some of it into new classes / interfaces.



      Now back to your particular example. If you absolutely must send a notification whenever a user is created and maybe even perform other more specialised actions, then you could create a separate service that encapsulates this requirement, something like UserCreationService, which exposes one method, Add(user), which handles both the storage (the call to your repository) and the notification as a single business action. Or do it in your original snippet, after _userRepository.SaveChanges();






      share|improve this answer


















      • 2





        Logging is not part of the business flow - how is this relevant in the context of SRP? If the purpose of my event would be to send new user data to Google Analytics - then disabling it would have the same business effect as disabling logging: not critical, but pretty upsetting. What is the rule of a thumb for adding/not adding new logic to a function? "Will disabling it cause major business side effects?"

        – Andre Borges
        19 hours ago













      6












      6








      6








      Is this a valid point?




      Yes it is, although it depends a lot on the structure of your code. I don't have the full context so I will try to talk in general.




      It seems to me that the raising an event here is essentially the same thing as logging: just adding some side functionality to the function.




      It absolutely isn't. Logging is not part of the business flow, it can be disabled, it shouldn't cause (business) side effects and should not influence the state and heath of your application in any way, even if you were for some reason not able to log anything anymore. Now compare that with the logic you added.




      And SRP does not prohibit you from using logging or firing events in your functions, it just says that such logic should be encapsulated in other classes, and it is OK for a repository to call these other classes.




      SRP works in tandem with ISP (S and I in SOLID). You end up with many classes and methods that do very specific things and nothing else. They are very focused, very easy to update or replace, and in general easy(er) to test. Of course in practice you'll also have a few bigger classes that deal with the orchestration: they will have a number of dependencies, and they will focus not on atomised actions, but on business actions, which may require multiple steps. As long as the business context is clear, they can too be called single responsibility, but as you correctly said, as the code grows, you may want to abstract some of it into new classes / interfaces.



      Now back to your particular example. If you absolutely must send a notification whenever a user is created and maybe even perform other more specialised actions, then you could create a separate service that encapsulates this requirement, something like UserCreationService, which exposes one method, Add(user), which handles both the storage (the call to your repository) and the notification as a single business action. Or do it in your original snippet, after _userRepository.SaveChanges();






      share|improve this answer














      Is this a valid point?




      Yes it is, although it depends a lot on the structure of your code. I don't have the full context so I will try to talk in general.




      It seems to me that the raising an event here is essentially the same thing as logging: just adding some side functionality to the function.




      It absolutely isn't. Logging is not part of the business flow, it can be disabled, it shouldn't cause (business) side effects and should not influence the state and heath of your application in any way, even if you were for some reason not able to log anything anymore. Now compare that with the logic you added.




      And SRP does not prohibit you from using logging or firing events in your functions, it just says that such logic should be encapsulated in other classes, and it is OK for a repository to call these other classes.




      SRP works in tandem with ISP (S and I in SOLID). You end up with many classes and methods that do very specific things and nothing else. They are very focused, very easy to update or replace, and in general easy(er) to test. Of course in practice you'll also have a few bigger classes that deal with the orchestration: they will have a number of dependencies, and they will focus not on atomised actions, but on business actions, which may require multiple steps. As long as the business context is clear, they can too be called single responsibility, but as you correctly said, as the code grows, you may want to abstract some of it into new classes / interfaces.



      Now back to your particular example. If you absolutely must send a notification whenever a user is created and maybe even perform other more specialised actions, then you could create a separate service that encapsulates this requirement, something like UserCreationService, which exposes one method, Add(user), which handles both the storage (the call to your repository) and the notification as a single business action. Or do it in your original snippet, after _userRepository.SaveChanges();







      share|improve this answer












      share|improve this answer



      share|improve this answer










      answered 21 hours ago









      asyncasync

      58259




      58259







      • 2





        Logging is not part of the business flow - how is this relevant in the context of SRP? If the purpose of my event would be to send new user data to Google Analytics - then disabling it would have the same business effect as disabling logging: not critical, but pretty upsetting. What is the rule of a thumb for adding/not adding new logic to a function? "Will disabling it cause major business side effects?"

        – Andre Borges
        19 hours ago












      • 2





        Logging is not part of the business flow - how is this relevant in the context of SRP? If the purpose of my event would be to send new user data to Google Analytics - then disabling it would have the same business effect as disabling logging: not critical, but pretty upsetting. What is the rule of a thumb for adding/not adding new logic to a function? "Will disabling it cause major business side effects?"

        – Andre Borges
        19 hours ago







      2




      2





      Logging is not part of the business flow - how is this relevant in the context of SRP? If the purpose of my event would be to send new user data to Google Analytics - then disabling it would have the same business effect as disabling logging: not critical, but pretty upsetting. What is the rule of a thumb for adding/not adding new logic to a function? "Will disabling it cause major business side effects?"

      – Andre Borges
      19 hours ago





      Logging is not part of the business flow - how is this relevant in the context of SRP? If the purpose of my event would be to send new user data to Google Analytics - then disabling it would have the same business effect as disabling logging: not critical, but pretty upsetting. What is the rule of a thumb for adding/not adding new logic to a function? "Will disabling it cause major business side effects?"

      – Andre Borges
      19 hours ago











      4














      Yes, it can be a valid requirement to have a repository which fires certain events on certain actions like Add or SaveChanges - and I am not going to question this (like some other answers), so let us assume this requirement is perfectly justified in the context of your system. And yes, encoding the event mechanics as well as the logging as well as the saving into one method violates the SRP (to a certain degree).



      The solution to this is to let your original repository stay to be responsible for committing changes to database, nothing else, and make a proxy repository which has exactly the same public interface, reuses the original repository and adds the additional event mechanics to the methods:



      // In EventFiringUserRepo:
      void SaveChanges()

      _basicRepo.SaveChanges();
      FireEventsForNewlyAddedUsers();


      void FireEventsForNewlyAddedUsers()

      foreach (var newUser in _basicRepo.DataContext.GetAddedUsers())

      _eventService.RaiseEvent(new UserCreatedEvent(newUser))




      The new and the old repository class should both derive from a common interface, like shown in the classic Proxy pattern description.



      Then, in your original code, initialize _userRepository by an object of the new EventFiringUserRepo class. That way, you keep the original repository separated from the event mechanics. If required, you can have the event firing repository and the original repository side-by-side and let the callers decide if they use either the former or the latter.



      If this kind of separation is really worth it in the context of your system is something you and your reviewer have to decide by yourself. I probably would not separate the logging from the original code using the same approach, though it would be possible in principle.






      share|improve this answer




















      • 2





        In addition to this answer. There are alternatives to proxies, like AOP.

        – Laiv
        10 hours ago







      • 1





        I think you miss the point, its not that raising an event breaks the SRP its that raising an event only for "New" users requires the repo to be responsible for knowing what constitutes a "New" user rather than a "Newly Added to Me" user

        – Ewan
        9 hours ago











      • @Ewan: please read the question again. It was about a place in code which does certain actions which needs to be coupled with other actions outside of the responsivility of that object. And putting the action and the event raising in one place was questioned by a peer reviewer as breaking the SRP. The example of "saving new users" is only for the purpose of demonstration, call the example contrived if you like, but that is IMHO not the point of the question.

        – Doc Brown
        4 hours ago






      • 1





        This is the best/correct answer IMO. Not only does it maintain SRP, but it also maintains the Open/Closed principle. And think of all of the automated tests that changes within the class could break. Modifying existing tests when new functionality is added is a big smell.

        – Keith Payne
        3 hours ago












      • This is an interesting answer, and a good illustration of the OCP. But, let's extrapolate. In a week, they want to add a "add this user to the you get a month free tier", and, after that month, add "don't forget to charge their credit card since they forgot to unsubscribe". Do you suggest "proxies all the way down"? (This is where I fear the OCP starts to fall apart)

        – user949300
        30 mins ago
















      4














      Yes, it can be a valid requirement to have a repository which fires certain events on certain actions like Add or SaveChanges - and I am not going to question this (like some other answers), so let us assume this requirement is perfectly justified in the context of your system. And yes, encoding the event mechanics as well as the logging as well as the saving into one method violates the SRP (to a certain degree).



      The solution to this is to let your original repository stay to be responsible for committing changes to database, nothing else, and make a proxy repository which has exactly the same public interface, reuses the original repository and adds the additional event mechanics to the methods:



      // In EventFiringUserRepo:
      void SaveChanges()

      _basicRepo.SaveChanges();
      FireEventsForNewlyAddedUsers();


      void FireEventsForNewlyAddedUsers()

      foreach (var newUser in _basicRepo.DataContext.GetAddedUsers())

      _eventService.RaiseEvent(new UserCreatedEvent(newUser))




      The new and the old repository class should both derive from a common interface, like shown in the classic Proxy pattern description.



      Then, in your original code, initialize _userRepository by an object of the new EventFiringUserRepo class. That way, you keep the original repository separated from the event mechanics. If required, you can have the event firing repository and the original repository side-by-side and let the callers decide if they use either the former or the latter.



      If this kind of separation is really worth it in the context of your system is something you and your reviewer have to decide by yourself. I probably would not separate the logging from the original code using the same approach, though it would be possible in principle.






      share|improve this answer




















      • 2





        In addition to this answer. There are alternatives to proxies, like AOP.

        – Laiv
        10 hours ago







      • 1





        I think you miss the point, its not that raising an event breaks the SRP its that raising an event only for "New" users requires the repo to be responsible for knowing what constitutes a "New" user rather than a "Newly Added to Me" user

        – Ewan
        9 hours ago











      • @Ewan: please read the question again. It was about a place in code which does certain actions which needs to be coupled with other actions outside of the responsivility of that object. And putting the action and the event raising in one place was questioned by a peer reviewer as breaking the SRP. The example of "saving new users" is only for the purpose of demonstration, call the example contrived if you like, but that is IMHO not the point of the question.

        – Doc Brown
        4 hours ago






      • 1





        This is the best/correct answer IMO. Not only does it maintain SRP, but it also maintains the Open/Closed principle. And think of all of the automated tests that changes within the class could break. Modifying existing tests when new functionality is added is a big smell.

        – Keith Payne
        3 hours ago












      • This is an interesting answer, and a good illustration of the OCP. But, let's extrapolate. In a week, they want to add a "add this user to the you get a month free tier", and, after that month, add "don't forget to charge their credit card since they forgot to unsubscribe". Do you suggest "proxies all the way down"? (This is where I fear the OCP starts to fall apart)

        – user949300
        30 mins ago














      4












      4








      4







      Yes, it can be a valid requirement to have a repository which fires certain events on certain actions like Add or SaveChanges - and I am not going to question this (like some other answers), so let us assume this requirement is perfectly justified in the context of your system. And yes, encoding the event mechanics as well as the logging as well as the saving into one method violates the SRP (to a certain degree).



      The solution to this is to let your original repository stay to be responsible for committing changes to database, nothing else, and make a proxy repository which has exactly the same public interface, reuses the original repository and adds the additional event mechanics to the methods:



      // In EventFiringUserRepo:
      void SaveChanges()

      _basicRepo.SaveChanges();
      FireEventsForNewlyAddedUsers();


      void FireEventsForNewlyAddedUsers()

      foreach (var newUser in _basicRepo.DataContext.GetAddedUsers())

      _eventService.RaiseEvent(new UserCreatedEvent(newUser))




      The new and the old repository class should both derive from a common interface, like shown in the classic Proxy pattern description.



      Then, in your original code, initialize _userRepository by an object of the new EventFiringUserRepo class. That way, you keep the original repository separated from the event mechanics. If required, you can have the event firing repository and the original repository side-by-side and let the callers decide if they use either the former or the latter.



      If this kind of separation is really worth it in the context of your system is something you and your reviewer have to decide by yourself. I probably would not separate the logging from the original code using the same approach, though it would be possible in principle.






      share|improve this answer















      Yes, it can be a valid requirement to have a repository which fires certain events on certain actions like Add or SaveChanges - and I am not going to question this (like some other answers), so let us assume this requirement is perfectly justified in the context of your system. And yes, encoding the event mechanics as well as the logging as well as the saving into one method violates the SRP (to a certain degree).



      The solution to this is to let your original repository stay to be responsible for committing changes to database, nothing else, and make a proxy repository which has exactly the same public interface, reuses the original repository and adds the additional event mechanics to the methods:



      // In EventFiringUserRepo:
      void SaveChanges()

      _basicRepo.SaveChanges();
      FireEventsForNewlyAddedUsers();


      void FireEventsForNewlyAddedUsers()

      foreach (var newUser in _basicRepo.DataContext.GetAddedUsers())

      _eventService.RaiseEvent(new UserCreatedEvent(newUser))




      The new and the old repository class should both derive from a common interface, like shown in the classic Proxy pattern description.



      Then, in your original code, initialize _userRepository by an object of the new EventFiringUserRepo class. That way, you keep the original repository separated from the event mechanics. If required, you can have the event firing repository and the original repository side-by-side and let the callers decide if they use either the former or the latter.



      If this kind of separation is really worth it in the context of your system is something you and your reviewer have to decide by yourself. I probably would not separate the logging from the original code using the same approach, though it would be possible in principle.







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited 2 hours ago









      Peter Mortensen

      1,11521114




      1,11521114










      answered 16 hours ago









      Doc BrownDoc Brown

      136k23251403




      136k23251403







      • 2





        In addition to this answer. There are alternatives to proxies, like AOP.

        – Laiv
        10 hours ago







      • 1





        I think you miss the point, its not that raising an event breaks the SRP its that raising an event only for "New" users requires the repo to be responsible for knowing what constitutes a "New" user rather than a "Newly Added to Me" user

        – Ewan
        9 hours ago











      • @Ewan: please read the question again. It was about a place in code which does certain actions which needs to be coupled with other actions outside of the responsivility of that object. And putting the action and the event raising in one place was questioned by a peer reviewer as breaking the SRP. The example of "saving new users" is only for the purpose of demonstration, call the example contrived if you like, but that is IMHO not the point of the question.

        – Doc Brown
        4 hours ago






      • 1





        This is the best/correct answer IMO. Not only does it maintain SRP, but it also maintains the Open/Closed principle. And think of all of the automated tests that changes within the class could break. Modifying existing tests when new functionality is added is a big smell.

        – Keith Payne
        3 hours ago












      • This is an interesting answer, and a good illustration of the OCP. But, let's extrapolate. In a week, they want to add a "add this user to the you get a month free tier", and, after that month, add "don't forget to charge their credit card since they forgot to unsubscribe". Do you suggest "proxies all the way down"? (This is where I fear the OCP starts to fall apart)

        – user949300
        30 mins ago













      • 2





        In addition to this answer. There are alternatives to proxies, like AOP.

        – Laiv
        10 hours ago







      • 1





        I think you miss the point, its not that raising an event breaks the SRP its that raising an event only for "New" users requires the repo to be responsible for knowing what constitutes a "New" user rather than a "Newly Added to Me" user

        – Ewan
        9 hours ago











      • @Ewan: please read the question again. It was about a place in code which does certain actions which needs to be coupled with other actions outside of the responsivility of that object. And putting the action and the event raising in one place was questioned by a peer reviewer as breaking the SRP. The example of "saving new users" is only for the purpose of demonstration, call the example contrived if you like, but that is IMHO not the point of the question.

        – Doc Brown
        4 hours ago






      • 1





        This is the best/correct answer IMO. Not only does it maintain SRP, but it also maintains the Open/Closed principle. And think of all of the automated tests that changes within the class could break. Modifying existing tests when new functionality is added is a big smell.

        – Keith Payne
        3 hours ago












      • This is an interesting answer, and a good illustration of the OCP. But, let's extrapolate. In a week, they want to add a "add this user to the you get a month free tier", and, after that month, add "don't forget to charge their credit card since they forgot to unsubscribe". Do you suggest "proxies all the way down"? (This is where I fear the OCP starts to fall apart)

        – user949300
        30 mins ago








      2




      2





      In addition to this answer. There are alternatives to proxies, like AOP.

      – Laiv
      10 hours ago






      In addition to this answer. There are alternatives to proxies, like AOP.

      – Laiv
      10 hours ago





      1




      1





      I think you miss the point, its not that raising an event breaks the SRP its that raising an event only for "New" users requires the repo to be responsible for knowing what constitutes a "New" user rather than a "Newly Added to Me" user

      – Ewan
      9 hours ago





      I think you miss the point, its not that raising an event breaks the SRP its that raising an event only for "New" users requires the repo to be responsible for knowing what constitutes a "New" user rather than a "Newly Added to Me" user

      – Ewan
      9 hours ago













      @Ewan: please read the question again. It was about a place in code which does certain actions which needs to be coupled with other actions outside of the responsivility of that object. And putting the action and the event raising in one place was questioned by a peer reviewer as breaking the SRP. The example of "saving new users" is only for the purpose of demonstration, call the example contrived if you like, but that is IMHO not the point of the question.

      – Doc Brown
      4 hours ago





      @Ewan: please read the question again. It was about a place in code which does certain actions which needs to be coupled with other actions outside of the responsivility of that object. And putting the action and the event raising in one place was questioned by a peer reviewer as breaking the SRP. The example of "saving new users" is only for the purpose of demonstration, call the example contrived if you like, but that is IMHO not the point of the question.

      – Doc Brown
      4 hours ago




      1




      1





      This is the best/correct answer IMO. Not only does it maintain SRP, but it also maintains the Open/Closed principle. And think of all of the automated tests that changes within the class could break. Modifying existing tests when new functionality is added is a big smell.

      – Keith Payne
      3 hours ago






      This is the best/correct answer IMO. Not only does it maintain SRP, but it also maintains the Open/Closed principle. And think of all of the automated tests that changes within the class could break. Modifying existing tests when new functionality is added is a big smell.

      – Keith Payne
      3 hours ago














      This is an interesting answer, and a good illustration of the OCP. But, let's extrapolate. In a week, they want to add a "add this user to the you get a month free tier", and, after that month, add "don't forget to charge their credit card since they forgot to unsubscribe". Do you suggest "proxies all the way down"? (This is where I fear the OCP starts to fall apart)

      – user949300
      30 mins ago






      This is an interesting answer, and a good illustration of the OCP. But, let's extrapolate. In a week, they want to add a "add this user to the you get a month free tier", and, after that month, add "don't forget to charge their credit card since they forgot to unsubscribe". Do you suggest "proxies all the way down"? (This is where I fear the OCP starts to fall apart)

      – user949300
      30 mins ago












      4














      SRP is, theoretically, about people. Uncle Bob Link added Here (Thanks Robert Harvey for providing it in your comment).



      The correct question is:



      Which "stakeholder" added the "send emails" requirement?



      If that stakeholder is also in charge of data persistence (unlikely but possible) then this does not violate SRP. Otherwise, it does.






      share|improve this answer




















      • 1





        Interesting - I never heard of this interpretation of the SRP. Do you have any pointers to more information / literature about this interpretation?

        – sleske
        4 hours ago






      • 1





        @sleske: From Uncle Bob himself: "And this gets to the crux of the Single Responsibility Principle. This principle is about people. When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function."

        – Robert Harvey
        1 hour ago












      • Thanks Robert. IMO, The name "Single Responsibility Principle" is terrible, as it sounds simple, but too few people follow up on what is the intended meaning of "responsibility". Kind of like how OOP has mutated from many of it's original concepts, and is now a fairly meaningless term.

        – user949300
        50 mins ago






      • 1





        Yep. That's what happened to the term REST. Even Roy Fielding says people are using it wrong.

        – Robert Harvey
        46 mins ago















      4














      SRP is, theoretically, about people. Uncle Bob Link added Here (Thanks Robert Harvey for providing it in your comment).



      The correct question is:



      Which "stakeholder" added the "send emails" requirement?



      If that stakeholder is also in charge of data persistence (unlikely but possible) then this does not violate SRP. Otherwise, it does.






      share|improve this answer




















      • 1





        Interesting - I never heard of this interpretation of the SRP. Do you have any pointers to more information / literature about this interpretation?

        – sleske
        4 hours ago






      • 1





        @sleske: From Uncle Bob himself: "And this gets to the crux of the Single Responsibility Principle. This principle is about people. When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function."

        – Robert Harvey
        1 hour ago












      • Thanks Robert. IMO, The name "Single Responsibility Principle" is terrible, as it sounds simple, but too few people follow up on what is the intended meaning of "responsibility". Kind of like how OOP has mutated from many of it's original concepts, and is now a fairly meaningless term.

        – user949300
        50 mins ago






      • 1





        Yep. That's what happened to the term REST. Even Roy Fielding says people are using it wrong.

        – Robert Harvey
        46 mins ago













      4












      4








      4







      SRP is, theoretically, about people. Uncle Bob Link added Here (Thanks Robert Harvey for providing it in your comment).



      The correct question is:



      Which "stakeholder" added the "send emails" requirement?



      If that stakeholder is also in charge of data persistence (unlikely but possible) then this does not violate SRP. Otherwise, it does.






      share|improve this answer















      SRP is, theoretically, about people. Uncle Bob Link added Here (Thanks Robert Harvey for providing it in your comment).



      The correct question is:



      Which "stakeholder" added the "send emails" requirement?



      If that stakeholder is also in charge of data persistence (unlikely but possible) then this does not violate SRP. Otherwise, it does.







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited 49 mins ago

























      answered 21 hours ago









      user949300user949300

      5,87511528




      5,87511528







      • 1





        Interesting - I never heard of this interpretation of the SRP. Do you have any pointers to more information / literature about this interpretation?

        – sleske
        4 hours ago






      • 1





        @sleske: From Uncle Bob himself: "And this gets to the crux of the Single Responsibility Principle. This principle is about people. When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function."

        – Robert Harvey
        1 hour ago












      • Thanks Robert. IMO, The name "Single Responsibility Principle" is terrible, as it sounds simple, but too few people follow up on what is the intended meaning of "responsibility". Kind of like how OOP has mutated from many of it's original concepts, and is now a fairly meaningless term.

        – user949300
        50 mins ago






      • 1





        Yep. That's what happened to the term REST. Even Roy Fielding says people are using it wrong.

        – Robert Harvey
        46 mins ago












      • 1





        Interesting - I never heard of this interpretation of the SRP. Do you have any pointers to more information / literature about this interpretation?

        – sleske
        4 hours ago






      • 1





        @sleske: From Uncle Bob himself: "And this gets to the crux of the Single Responsibility Principle. This principle is about people. When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function."

        – Robert Harvey
        1 hour ago












      • Thanks Robert. IMO, The name "Single Responsibility Principle" is terrible, as it sounds simple, but too few people follow up on what is the intended meaning of "responsibility". Kind of like how OOP has mutated from many of it's original concepts, and is now a fairly meaningless term.

        – user949300
        50 mins ago






      • 1





        Yep. That's what happened to the term REST. Even Roy Fielding says people are using it wrong.

        – Robert Harvey
        46 mins ago







      1




      1





      Interesting - I never heard of this interpretation of the SRP. Do you have any pointers to more information / literature about this interpretation?

      – sleske
      4 hours ago





      Interesting - I never heard of this interpretation of the SRP. Do you have any pointers to more information / literature about this interpretation?

      – sleske
      4 hours ago




      1




      1





      @sleske: From Uncle Bob himself: "And this gets to the crux of the Single Responsibility Principle. This principle is about people. When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function."

      – Robert Harvey
      1 hour ago






      @sleske: From Uncle Bob himself: "And this gets to the crux of the Single Responsibility Principle. This principle is about people. When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function."

      – Robert Harvey
      1 hour ago














      Thanks Robert. IMO, The name "Single Responsibility Principle" is terrible, as it sounds simple, but too few people follow up on what is the intended meaning of "responsibility". Kind of like how OOP has mutated from many of it's original concepts, and is now a fairly meaningless term.

      – user949300
      50 mins ago





      Thanks Robert. IMO, The name "Single Responsibility Principle" is terrible, as it sounds simple, but too few people follow up on what is the intended meaning of "responsibility". Kind of like how OOP has mutated from many of it's original concepts, and is now a fairly meaningless term.

      – user949300
      50 mins ago




      1




      1





      Yep. That's what happened to the term REST. Even Roy Fielding says people are using it wrong.

      – Robert Harvey
      46 mins ago





      Yep. That's what happened to the term REST. Even Roy Fielding says people are using it wrong.

      – Robert Harvey
      46 mins ago











      1














      While technically there's nothing wrong with repositories notifying events, I would suggest looking at it from a functional point of view where its convenience rises some concerns.




      Creating users is one thing, its persistence a different one.




      Premise of mine



      Consider the previous premise before deciding whether the repository is the proper place to notify business events (regardless of the SRP). Note that I said business event because to me UserCreated has a different connotation than UserStored or UserAdded 1. I would also consider each of those events to be addressed to different audiences.



      On one side, creating users is a business-specific rule that might or might not involve persistence. It might involve more business operations, involving more database/network operations. Operations the persistence layer is unaware of. The persistence layer doesn't have enough context to decide whether the use case ended successfully or not.



      On the flip side, it's not necessarily true that _dataContext.SaveChanges(); has persisted the user successfully. It will depend on the database's transaction span. For instance, it could be true for databases like MongoDB, which transactions are atomic, but it could not, for traditional RDBMS implementing ACID transactions where there could be more transactions involved and yet to be committed.




      Is this a valid point?




      I would dare to say that, It's not only a matter of SRP (technically speaking), It's also a matter of convenience (functionally speaking). Is it convenient to cast business events from components that are not totally aware of the business operations in progress? Do they represent the right place as much as the right moment? Should the persistence orchestrate my business through notifications like this? Can we invalidate premature events?* 2




      It seems to me that the raising an event here is essentially the same
      thing as logging




      Absolutely not. Logging is meant to have no side effects, however, I suspect your event UserCreated is likely to cause other business operations to happen. Like notifications. 3




      it just says that such logic should be encapsulated in other classes,
      and it is OK for a repository to call these other classes




      Not necessarily true. SRP is not a class-specific concern only. It operates at different levels of abstractions, like layers, libraries and systems! It's about cohesion, about keeping together what changes for the same reasons. If the user creation (use case) changes it's likely the moment and the reasons for the event to happen also changes.




      1: Naming things adequately also matters.



      2: Say we sent UserCreated after _dataContext.SaveChanges();, but the whole database transaction failed later due to connection issues or constraints violations. Be careful with premature broadcasting of events, because its side effects can be hard to undo (if that is even possible).



      3: Bear in mind that a sent email cannot be undon






      share|improve this answer

























      • +1 Very good point about the transaction span. It can be premature to assert the user has been created, because rollbacks can happen; and unlike with a log, it's likely some other part of the app does something with the event.

        – Andres F.
        22 hours ago






      • 1





        Exactly. Events denote certainity. Something happened but it's over.

        – Laiv
        22 hours ago












      • @Laiv: Except when they don't. Microsoft has all sorts of events prefixed with Before or Preview that make no guarantees at all about certainty.

        – Robert Harvey
        22 hours ago











      • Hmm. I have never seen this sort of events. Would you mind sharing some references?

        – Laiv
        21 hours ago






      • 1





        @RobertHarvey No, my answer is, "Learn to think like Python developers think and then apply those lessons in C#." Sadly, this is not a common approach, even though I can personally attest to the improvement to my own C# code doing it. (That is true in spite of the ecosystem, and I find other devs that I've worked with also appreciated the approaches I used and suggested.)

        – jpmc26
        1 hour ago
















      1














      While technically there's nothing wrong with repositories notifying events, I would suggest looking at it from a functional point of view where its convenience rises some concerns.




      Creating users is one thing, its persistence a different one.




      Premise of mine



      Consider the previous premise before deciding whether the repository is the proper place to notify business events (regardless of the SRP). Note that I said business event because to me UserCreated has a different connotation than UserStored or UserAdded 1. I would also consider each of those events to be addressed to different audiences.



      On one side, creating users is a business-specific rule that might or might not involve persistence. It might involve more business operations, involving more database/network operations. Operations the persistence layer is unaware of. The persistence layer doesn't have enough context to decide whether the use case ended successfully or not.



      On the flip side, it's not necessarily true that _dataContext.SaveChanges(); has persisted the user successfully. It will depend on the database's transaction span. For instance, it could be true for databases like MongoDB, which transactions are atomic, but it could not, for traditional RDBMS implementing ACID transactions where there could be more transactions involved and yet to be committed.




      Is this a valid point?




      I would dare to say that, It's not only a matter of SRP (technically speaking), It's also a matter of convenience (functionally speaking). Is it convenient to cast business events from components that are not totally aware of the business operations in progress? Do they represent the right place as much as the right moment? Should the persistence orchestrate my business through notifications like this? Can we invalidate premature events?* 2




      It seems to me that the raising an event here is essentially the same
      thing as logging




      Absolutely not. Logging is meant to have no side effects, however, I suspect your event UserCreated is likely to cause other business operations to happen. Like notifications. 3




      it just says that such logic should be encapsulated in other classes,
      and it is OK for a repository to call these other classes




      Not necessarily true. SRP is not a class-specific concern only. It operates at different levels of abstractions, like layers, libraries and systems! It's about cohesion, about keeping together what changes for the same reasons. If the user creation (use case) changes it's likely the moment and the reasons for the event to happen also changes.




      1: Naming things adequately also matters.



      2: Say we sent UserCreated after _dataContext.SaveChanges();, but the whole database transaction failed later due to connection issues or constraints violations. Be careful with premature broadcasting of events, because its side effects can be hard to undo (if that is even possible).



      3: Bear in mind that a sent email cannot be undon






      share|improve this answer

























      • +1 Very good point about the transaction span. It can be premature to assert the user has been created, because rollbacks can happen; and unlike with a log, it's likely some other part of the app does something with the event.

        – Andres F.
        22 hours ago






      • 1





        Exactly. Events denote certainity. Something happened but it's over.

        – Laiv
        22 hours ago












      • @Laiv: Except when they don't. Microsoft has all sorts of events prefixed with Before or Preview that make no guarantees at all about certainty.

        – Robert Harvey
        22 hours ago











      • Hmm. I have never seen this sort of events. Would you mind sharing some references?

        – Laiv
        21 hours ago






      • 1





        @RobertHarvey No, my answer is, "Learn to think like Python developers think and then apply those lessons in C#." Sadly, this is not a common approach, even though I can personally attest to the improvement to my own C# code doing it. (That is true in spite of the ecosystem, and I find other devs that I've worked with also appreciated the approaches I used and suggested.)

        – jpmc26
        1 hour ago














      1












      1








      1







      While technically there's nothing wrong with repositories notifying events, I would suggest looking at it from a functional point of view where its convenience rises some concerns.




      Creating users is one thing, its persistence a different one.




      Premise of mine



      Consider the previous premise before deciding whether the repository is the proper place to notify business events (regardless of the SRP). Note that I said business event because to me UserCreated has a different connotation than UserStored or UserAdded 1. I would also consider each of those events to be addressed to different audiences.



      On one side, creating users is a business-specific rule that might or might not involve persistence. It might involve more business operations, involving more database/network operations. Operations the persistence layer is unaware of. The persistence layer doesn't have enough context to decide whether the use case ended successfully or not.



      On the flip side, it's not necessarily true that _dataContext.SaveChanges(); has persisted the user successfully. It will depend on the database's transaction span. For instance, it could be true for databases like MongoDB, which transactions are atomic, but it could not, for traditional RDBMS implementing ACID transactions where there could be more transactions involved and yet to be committed.




      Is this a valid point?




      I would dare to say that, It's not only a matter of SRP (technically speaking), It's also a matter of convenience (functionally speaking). Is it convenient to cast business events from components that are not totally aware of the business operations in progress? Do they represent the right place as much as the right moment? Should the persistence orchestrate my business through notifications like this? Can we invalidate premature events?* 2




      It seems to me that the raising an event here is essentially the same
      thing as logging




      Absolutely not. Logging is meant to have no side effects, however, I suspect your event UserCreated is likely to cause other business operations to happen. Like notifications. 3




      it just says that such logic should be encapsulated in other classes,
      and it is OK for a repository to call these other classes




      Not necessarily true. SRP is not a class-specific concern only. It operates at different levels of abstractions, like layers, libraries and systems! It's about cohesion, about keeping together what changes for the same reasons. If the user creation (use case) changes it's likely the moment and the reasons for the event to happen also changes.




      1: Naming things adequately also matters.



      2: Say we sent UserCreated after _dataContext.SaveChanges();, but the whole database transaction failed later due to connection issues or constraints violations. Be careful with premature broadcasting of events, because its side effects can be hard to undo (if that is even possible).



      3: Bear in mind that a sent email cannot be undon






      share|improve this answer















      While technically there's nothing wrong with repositories notifying events, I would suggest looking at it from a functional point of view where its convenience rises some concerns.




      Creating users is one thing, its persistence a different one.




      Premise of mine



      Consider the previous premise before deciding whether the repository is the proper place to notify business events (regardless of the SRP). Note that I said business event because to me UserCreated has a different connotation than UserStored or UserAdded 1. I would also consider each of those events to be addressed to different audiences.



      On one side, creating users is a business-specific rule that might or might not involve persistence. It might involve more business operations, involving more database/network operations. Operations the persistence layer is unaware of. The persistence layer doesn't have enough context to decide whether the use case ended successfully or not.



      On the flip side, it's not necessarily true that _dataContext.SaveChanges(); has persisted the user successfully. It will depend on the database's transaction span. For instance, it could be true for databases like MongoDB, which transactions are atomic, but it could not, for traditional RDBMS implementing ACID transactions where there could be more transactions involved and yet to be committed.




      Is this a valid point?




      I would dare to say that, It's not only a matter of SRP (technically speaking), It's also a matter of convenience (functionally speaking). Is it convenient to cast business events from components that are not totally aware of the business operations in progress? Do they represent the right place as much as the right moment? Should the persistence orchestrate my business through notifications like this? Can we invalidate premature events?* 2




      It seems to me that the raising an event here is essentially the same
      thing as logging




      Absolutely not. Logging is meant to have no side effects, however, I suspect your event UserCreated is likely to cause other business operations to happen. Like notifications. 3




      it just says that such logic should be encapsulated in other classes,
      and it is OK for a repository to call these other classes




      Not necessarily true. SRP is not a class-specific concern only. It operates at different levels of abstractions, like layers, libraries and systems! It's about cohesion, about keeping together what changes for the same reasons. If the user creation (use case) changes it's likely the moment and the reasons for the event to happen also changes.




      1: Naming things adequately also matters.



      2: Say we sent UserCreated after _dataContext.SaveChanges();, but the whole database transaction failed later due to connection issues or constraints violations. Be careful with premature broadcasting of events, because its side effects can be hard to undo (if that is even possible).



      3: Bear in mind that a sent email cannot be undon







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited 10 hours ago

























      answered 23 hours ago









      LaivLaiv

      6,89311241




      6,89311241












      • +1 Very good point about the transaction span. It can be premature to assert the user has been created, because rollbacks can happen; and unlike with a log, it's likely some other part of the app does something with the event.

        – Andres F.
        22 hours ago






      • 1





        Exactly. Events denote certainity. Something happened but it's over.

        – Laiv
        22 hours ago












      • @Laiv: Except when they don't. Microsoft has all sorts of events prefixed with Before or Preview that make no guarantees at all about certainty.

        – Robert Harvey
        22 hours ago











      • Hmm. I have never seen this sort of events. Would you mind sharing some references?

        – Laiv
        21 hours ago






      • 1





        @RobertHarvey No, my answer is, "Learn to think like Python developers think and then apply those lessons in C#." Sadly, this is not a common approach, even though I can personally attest to the improvement to my own C# code doing it. (That is true in spite of the ecosystem, and I find other devs that I've worked with also appreciated the approaches I used and suggested.)

        – jpmc26
        1 hour ago


















      • +1 Very good point about the transaction span. It can be premature to assert the user has been created, because rollbacks can happen; and unlike with a log, it's likely some other part of the app does something with the event.

        – Andres F.
        22 hours ago






      • 1





        Exactly. Events denote certainity. Something happened but it's over.

        – Laiv
        22 hours ago












      • @Laiv: Except when they don't. Microsoft has all sorts of events prefixed with Before or Preview that make no guarantees at all about certainty.

        – Robert Harvey
        22 hours ago











      • Hmm. I have never seen this sort of events. Would you mind sharing some references?

        – Laiv
        21 hours ago






      • 1





        @RobertHarvey No, my answer is, "Learn to think like Python developers think and then apply those lessons in C#." Sadly, this is not a common approach, even though I can personally attest to the improvement to my own C# code doing it. (That is true in spite of the ecosystem, and I find other devs that I've worked with also appreciated the approaches I used and suggested.)

        – jpmc26
        1 hour ago

















      +1 Very good point about the transaction span. It can be premature to assert the user has been created, because rollbacks can happen; and unlike with a log, it's likely some other part of the app does something with the event.

      – Andres F.
      22 hours ago





      +1 Very good point about the transaction span. It can be premature to assert the user has been created, because rollbacks can happen; and unlike with a log, it's likely some other part of the app does something with the event.

      – Andres F.
      22 hours ago




      1




      1





      Exactly. Events denote certainity. Something happened but it's over.

      – Laiv
      22 hours ago






      Exactly. Events denote certainity. Something happened but it's over.

      – Laiv
      22 hours ago














      @Laiv: Except when they don't. Microsoft has all sorts of events prefixed with Before or Preview that make no guarantees at all about certainty.

      – Robert Harvey
      22 hours ago





      @Laiv: Except when they don't. Microsoft has all sorts of events prefixed with Before or Preview that make no guarantees at all about certainty.

      – Robert Harvey
      22 hours ago













      Hmm. I have never seen this sort of events. Would you mind sharing some references?

      – Laiv
      21 hours ago





      Hmm. I have never seen this sort of events. Would you mind sharing some references?

      – Laiv
      21 hours ago




      1




      1





      @RobertHarvey No, my answer is, "Learn to think like Python developers think and then apply those lessons in C#." Sadly, this is not a common approach, even though I can personally attest to the improvement to my own C# code doing it. (That is true in spite of the ecosystem, and I find other devs that I've worked with also appreciated the approaches I used and suggested.)

      – jpmc26
      1 hour ago






      @RobertHarvey No, my answer is, "Learn to think like Python developers think and then apply those lessons in C#." Sadly, this is not a common approach, even though I can personally attest to the improvement to my own C# code doing it. (That is true in spite of the ecosystem, and I find other devs that I've worked with also appreciated the approaches I used and suggested.)

      – jpmc26
      1 hour ago












      1














      Don't worry about single responsibility principle. It's not going to help you make a good decision here because you can subjectively choose a particular concept as a "responsibility." You could say the class' responsibility is managing data persistence to the database, or you could say its responsibility is to perform all the work related to creating a user. These are just different levels of the application's behavior, and they're both valid conceptual expressions of a "single responsibility." So this principle is unhelpful for solving your problem.



      The most useful principle to apply in this case is the principle of least surprise. So let's ask the question: is it surprising that a repository with the primary role of persisting data to a database also sends e-mails?



      Yes, it very much is surprising. These are two completely separate external systems, and the name SaveChanges does not imply also sending notifications. The fact you delegate this out to an event makes the behavior even more surprising, since someone reading the code can no longer easily see what additional behaviors are invoked. Indirection harms readability. Sometimes, the benefits are worth the readability costs, but not when you're automatically invoking an additional external system that has effects observable to end users. (Logging can be excluded here since its effect is essentially record keeping for debugging purposes. End users do not consume the log, so there is no harm in always logging.) Even worse, this reduces flexibility in the timing of sending the e-mail, making it impossible to interleave other operations between the save and the notification.



      If your code typically needs to send a notification when a user is successfully created, you could create a method that does so:



      public void AddUserAndNotify(IUserRepository repo, IEmailNotification notifier, MyUser user)

      repo.Add(user);
      repo.SaveChanges();
      notifier.SendUserCreatedNotification(user);



      But whether this adds value depends on your application's specifics.




      I'd actually discourage the existence of the SaveChanges method at all. This method will presumably commit a database transaction, but other repositories might have modified the database in the same transaction. The fact it commits all of them is again surprising, since SaveChanges is specifically tied to this instance of the user repository.



      The most straightforward pattern for managing a database transaction is an outer using block:



      using (DataContext context = new DataContext())

      _userRepository.Add(context, user);
      context.SaveChanges();
      notifier.SendUserCreatedNotification(user);



      This gives the programmer explicit control over when changes for all repositories are saved, forces the code to explicitly document the sequence of events that must occur before a commit, ensures a rollback is issued on error (assuming that DataContext.Dispose issues a rollback), and avoids hidden connections between stateful classes.



      I'd also prefer not to send the e-mail directly in the request. It would be more robust to record the need for a notification in a queue. This would allow for better failure handling. In particular, if an error occurs sending the e-mail, it can be tried again later without interrupting saving the user, and it avoids the case where the user is created but an error is returned by the site.



      using (DataContext context = new DataContext())

      _userRepository.Add(context, user);
      _emailNotificationQueue.AddUserCreateNotification(user);
      _emailNotificationQueue.Commit();
      context.SaveChanges();



      It's better to commit the notification queue first since the queue's consumer can verify that the user exists before sending the e-mail, in the event that the context.SaveChanges() call fails. (Otherwise, you'll need a full blown two-phase commit strategy to avoid heisenbugs.)




      The bottom line is to be practical. Actually think through the consequences (both in terms of risk and benefit) of writing code a particular way. I find that "single responsibility principle" doesn't very often help me do that, while "principle of least surprise" often helps me get into another developer's head (so to speak) and think about what might happen.






      share|improve this answer




















      • 1





        is it surprising that a repository with the primary role of persisting data to a database also sends e-mails - I think you missed the point of my question. My repository is not sending emails. It just raises an event, and how this event is handled - is the responsibility of external code.

        – Andre Borges
        1 hour ago






      • 1





        @AndreBorges My answer says, "The fact you delegate this out to an event makes the behavior even more surprising, since someone reading the code can no longer easily see what additional behaviors are invoked. Indirection harms readability." You're still thinking in terms of SRP. The whole point of my answer is that thinking that way is not helping you write better code. Good code makes most of what someone else needs to know about it obvious when they read it, and the way you've structured your code does not do so.

        – jpmc26
        1 hour ago












      • You're basically making the argument "don't use events."

        – Robert Harvey
        1 hour ago











      • @RobertHarvey Yup. They make the connections between different parts of code much harder to discover, which makes understanding the sequence of events that occurs a much more difficult task. As I said, sometimes it's worth that price, but it's definitely a price you want to avoid paying whenever you can (which is usually in my experience).

        – jpmc26
        1 hour ago







      • 1





        @RobertHarvey thinks Actually, I take it back. I'm not arguing against events. What I'm arguing against in this answer is indirection that is unnecessary. All events do in this case is get your code out of sequence. The repository is not waiting for something to happen; it's performing an operation on request. Events should be limited to triggering behavior that occurs at a time under the control of an external source (such as a user). A web request handler is essentially sequential: once the request is received, perform operations in order until a response is generated and returned.

        – jpmc26
        34 mins ago
















      1














      Don't worry about single responsibility principle. It's not going to help you make a good decision here because you can subjectively choose a particular concept as a "responsibility." You could say the class' responsibility is managing data persistence to the database, or you could say its responsibility is to perform all the work related to creating a user. These are just different levels of the application's behavior, and they're both valid conceptual expressions of a "single responsibility." So this principle is unhelpful for solving your problem.



      The most useful principle to apply in this case is the principle of least surprise. So let's ask the question: is it surprising that a repository with the primary role of persisting data to a database also sends e-mails?



      Yes, it very much is surprising. These are two completely separate external systems, and the name SaveChanges does not imply also sending notifications. The fact you delegate this out to an event makes the behavior even more surprising, since someone reading the code can no longer easily see what additional behaviors are invoked. Indirection harms readability. Sometimes, the benefits are worth the readability costs, but not when you're automatically invoking an additional external system that has effects observable to end users. (Logging can be excluded here since its effect is essentially record keeping for debugging purposes. End users do not consume the log, so there is no harm in always logging.) Even worse, this reduces flexibility in the timing of sending the e-mail, making it impossible to interleave other operations between the save and the notification.



      If your code typically needs to send a notification when a user is successfully created, you could create a method that does so:



      public void AddUserAndNotify(IUserRepository repo, IEmailNotification notifier, MyUser user)

      repo.Add(user);
      repo.SaveChanges();
      notifier.SendUserCreatedNotification(user);



      But whether this adds value depends on your application's specifics.




      I'd actually discourage the existence of the SaveChanges method at all. This method will presumably commit a database transaction, but other repositories might have modified the database in the same transaction. The fact it commits all of them is again surprising, since SaveChanges is specifically tied to this instance of the user repository.



      The most straightforward pattern for managing a database transaction is an outer using block:



      using (DataContext context = new DataContext())

      _userRepository.Add(context, user);
      context.SaveChanges();
      notifier.SendUserCreatedNotification(user);



      This gives the programmer explicit control over when changes for all repositories are saved, forces the code to explicitly document the sequence of events that must occur before a commit, ensures a rollback is issued on error (assuming that DataContext.Dispose issues a rollback), and avoids hidden connections between stateful classes.



      I'd also prefer not to send the e-mail directly in the request. It would be more robust to record the need for a notification in a queue. This would allow for better failure handling. In particular, if an error occurs sending the e-mail, it can be tried again later without interrupting saving the user, and it avoids the case where the user is created but an error is returned by the site.



      using (DataContext context = new DataContext())

      _userRepository.Add(context, user);
      _emailNotificationQueue.AddUserCreateNotification(user);
      _emailNotificationQueue.Commit();
      context.SaveChanges();



      It's better to commit the notification queue first since the queue's consumer can verify that the user exists before sending the e-mail, in the event that the context.SaveChanges() call fails. (Otherwise, you'll need a full blown two-phase commit strategy to avoid heisenbugs.)




      The bottom line is to be practical. Actually think through the consequences (both in terms of risk and benefit) of writing code a particular way. I find that "single responsibility principle" doesn't very often help me do that, while "principle of least surprise" often helps me get into another developer's head (so to speak) and think about what might happen.






      share|improve this answer




















      • 1





        is it surprising that a repository with the primary role of persisting data to a database also sends e-mails - I think you missed the point of my question. My repository is not sending emails. It just raises an event, and how this event is handled - is the responsibility of external code.

        – Andre Borges
        1 hour ago






      • 1





        @AndreBorges My answer says, "The fact you delegate this out to an event makes the behavior even more surprising, since someone reading the code can no longer easily see what additional behaviors are invoked. Indirection harms readability." You're still thinking in terms of SRP. The whole point of my answer is that thinking that way is not helping you write better code. Good code makes most of what someone else needs to know about it obvious when they read it, and the way you've structured your code does not do so.

        – jpmc26
        1 hour ago












      • You're basically making the argument "don't use events."

        – Robert Harvey
        1 hour ago











      • @RobertHarvey Yup. They make the connections between different parts of code much harder to discover, which makes understanding the sequence of events that occurs a much more difficult task. As I said, sometimes it's worth that price, but it's definitely a price you want to avoid paying whenever you can (which is usually in my experience).

        – jpmc26
        1 hour ago







      • 1





        @RobertHarvey thinks Actually, I take it back. I'm not arguing against events. What I'm arguing against in this answer is indirection that is unnecessary. All events do in this case is get your code out of sequence. The repository is not waiting for something to happen; it's performing an operation on request. Events should be limited to triggering behavior that occurs at a time under the control of an external source (such as a user). A web request handler is essentially sequential: once the request is received, perform operations in order until a response is generated and returned.

        – jpmc26
        34 mins ago














      1












      1








      1







      Don't worry about single responsibility principle. It's not going to help you make a good decision here because you can subjectively choose a particular concept as a "responsibility." You could say the class' responsibility is managing data persistence to the database, or you could say its responsibility is to perform all the work related to creating a user. These are just different levels of the application's behavior, and they're both valid conceptual expressions of a "single responsibility." So this principle is unhelpful for solving your problem.



      The most useful principle to apply in this case is the principle of least surprise. So let's ask the question: is it surprising that a repository with the primary role of persisting data to a database also sends e-mails?



      Yes, it very much is surprising. These are two completely separate external systems, and the name SaveChanges does not imply also sending notifications. The fact you delegate this out to an event makes the behavior even more surprising, since someone reading the code can no longer easily see what additional behaviors are invoked. Indirection harms readability. Sometimes, the benefits are worth the readability costs, but not when you're automatically invoking an additional external system that has effects observable to end users. (Logging can be excluded here since its effect is essentially record keeping for debugging purposes. End users do not consume the log, so there is no harm in always logging.) Even worse, this reduces flexibility in the timing of sending the e-mail, making it impossible to interleave other operations between the save and the notification.



      If your code typically needs to send a notification when a user is successfully created, you could create a method that does so:



      public void AddUserAndNotify(IUserRepository repo, IEmailNotification notifier, MyUser user)

      repo.Add(user);
      repo.SaveChanges();
      notifier.SendUserCreatedNotification(user);



      But whether this adds value depends on your application's specifics.




      I'd actually discourage the existence of the SaveChanges method at all. This method will presumably commit a database transaction, but other repositories might have modified the database in the same transaction. The fact it commits all of them is again surprising, since SaveChanges is specifically tied to this instance of the user repository.



      The most straightforward pattern for managing a database transaction is an outer using block:



      using (DataContext context = new DataContext())

      _userRepository.Add(context, user);
      context.SaveChanges();
      notifier.SendUserCreatedNotification(user);



      This gives the programmer explicit control over when changes for all repositories are saved, forces the code to explicitly document the sequence of events that must occur before a commit, ensures a rollback is issued on error (assuming that DataContext.Dispose issues a rollback), and avoids hidden connections between stateful classes.



      I'd also prefer not to send the e-mail directly in the request. It would be more robust to record the need for a notification in a queue. This would allow for better failure handling. In particular, if an error occurs sending the e-mail, it can be tried again later without interrupting saving the user, and it avoids the case where the user is created but an error is returned by the site.



      using (DataContext context = new DataContext())

      _userRepository.Add(context, user);
      _emailNotificationQueue.AddUserCreateNotification(user);
      _emailNotificationQueue.Commit();
      context.SaveChanges();



      It's better to commit the notification queue first since the queue's consumer can verify that the user exists before sending the e-mail, in the event that the context.SaveChanges() call fails. (Otherwise, you'll need a full blown two-phase commit strategy to avoid heisenbugs.)




      The bottom line is to be practical. Actually think through the consequences (both in terms of risk and benefit) of writing code a particular way. I find that "single responsibility principle" doesn't very often help me do that, while "principle of least surprise" often helps me get into another developer's head (so to speak) and think about what might happen.






      share|improve this answer















      Don't worry about single responsibility principle. It's not going to help you make a good decision here because you can subjectively choose a particular concept as a "responsibility." You could say the class' responsibility is managing data persistence to the database, or you could say its responsibility is to perform all the work related to creating a user. These are just different levels of the application's behavior, and they're both valid conceptual expressions of a "single responsibility." So this principle is unhelpful for solving your problem.



      The most useful principle to apply in this case is the principle of least surprise. So let's ask the question: is it surprising that a repository with the primary role of persisting data to a database also sends e-mails?



      Yes, it very much is surprising. These are two completely separate external systems, and the name SaveChanges does not imply also sending notifications. The fact you delegate this out to an event makes the behavior even more surprising, since someone reading the code can no longer easily see what additional behaviors are invoked. Indirection harms readability. Sometimes, the benefits are worth the readability costs, but not when you're automatically invoking an additional external system that has effects observable to end users. (Logging can be excluded here since its effect is essentially record keeping for debugging purposes. End users do not consume the log, so there is no harm in always logging.) Even worse, this reduces flexibility in the timing of sending the e-mail, making it impossible to interleave other operations between the save and the notification.



      If your code typically needs to send a notification when a user is successfully created, you could create a method that does so:



      public void AddUserAndNotify(IUserRepository repo, IEmailNotification notifier, MyUser user)

      repo.Add(user);
      repo.SaveChanges();
      notifier.SendUserCreatedNotification(user);



      But whether this adds value depends on your application's specifics.




      I'd actually discourage the existence of the SaveChanges method at all. This method will presumably commit a database transaction, but other repositories might have modified the database in the same transaction. The fact it commits all of them is again surprising, since SaveChanges is specifically tied to this instance of the user repository.



      The most straightforward pattern for managing a database transaction is an outer using block:



      using (DataContext context = new DataContext())

      _userRepository.Add(context, user);
      context.SaveChanges();
      notifier.SendUserCreatedNotification(user);



      This gives the programmer explicit control over when changes for all repositories are saved, forces the code to explicitly document the sequence of events that must occur before a commit, ensures a rollback is issued on error (assuming that DataContext.Dispose issues a rollback), and avoids hidden connections between stateful classes.



      I'd also prefer not to send the e-mail directly in the request. It would be more robust to record the need for a notification in a queue. This would allow for better failure handling. In particular, if an error occurs sending the e-mail, it can be tried again later without interrupting saving the user, and it avoids the case where the user is created but an error is returned by the site.



      using (DataContext context = new DataContext())

      _userRepository.Add(context, user);
      _emailNotificationQueue.AddUserCreateNotification(user);
      _emailNotificationQueue.Commit();
      context.SaveChanges();



      It's better to commit the notification queue first since the queue's consumer can verify that the user exists before sending the e-mail, in the event that the context.SaveChanges() call fails. (Otherwise, you'll need a full blown two-phase commit strategy to avoid heisenbugs.)




      The bottom line is to be practical. Actually think through the consequences (both in terms of risk and benefit) of writing code a particular way. I find that "single responsibility principle" doesn't very often help me do that, while "principle of least surprise" often helps me get into another developer's head (so to speak) and think about what might happen.







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited 1 hour ago

























      answered 2 hours ago









      jpmc26jpmc26

      3,93121735




      3,93121735







      • 1





        is it surprising that a repository with the primary role of persisting data to a database also sends e-mails - I think you missed the point of my question. My repository is not sending emails. It just raises an event, and how this event is handled - is the responsibility of external code.

        – Andre Borges
        1 hour ago






      • 1





        @AndreBorges My answer says, "The fact you delegate this out to an event makes the behavior even more surprising, since someone reading the code can no longer easily see what additional behaviors are invoked. Indirection harms readability." You're still thinking in terms of SRP. The whole point of my answer is that thinking that way is not helping you write better code. Good code makes most of what someone else needs to know about it obvious when they read it, and the way you've structured your code does not do so.

        – jpmc26
        1 hour ago












      • You're basically making the argument "don't use events."

        – Robert Harvey
        1 hour ago











      • @RobertHarvey Yup. They make the connections between different parts of code much harder to discover, which makes understanding the sequence of events that occurs a much more difficult task. As I said, sometimes it's worth that price, but it's definitely a price you want to avoid paying whenever you can (which is usually in my experience).

        – jpmc26
        1 hour ago







      • 1





        @RobertHarvey thinks Actually, I take it back. I'm not arguing against events. What I'm arguing against in this answer is indirection that is unnecessary. All events do in this case is get your code out of sequence. The repository is not waiting for something to happen; it's performing an operation on request. Events should be limited to triggering behavior that occurs at a time under the control of an external source (such as a user). A web request handler is essentially sequential: once the request is received, perform operations in order until a response is generated and returned.

        – jpmc26
        34 mins ago













      • 1





        is it surprising that a repository with the primary role of persisting data to a database also sends e-mails - I think you missed the point of my question. My repository is not sending emails. It just raises an event, and how this event is handled - is the responsibility of external code.

        – Andre Borges
        1 hour ago






      • 1





        @AndreBorges My answer says, "The fact you delegate this out to an event makes the behavior even more surprising, since someone reading the code can no longer easily see what additional behaviors are invoked. Indirection harms readability." You're still thinking in terms of SRP. The whole point of my answer is that thinking that way is not helping you write better code. Good code makes most of what someone else needs to know about it obvious when they read it, and the way you've structured your code does not do so.

        – jpmc26
        1 hour ago












      • You're basically making the argument "don't use events."

        – Robert Harvey
        1 hour ago











      • @RobertHarvey Yup. They make the connections between different parts of code much harder to discover, which makes understanding the sequence of events that occurs a much more difficult task. As I said, sometimes it's worth that price, but it's definitely a price you want to avoid paying whenever you can (which is usually in my experience).

        – jpmc26
        1 hour ago







      • 1





        @RobertHarvey thinks Actually, I take it back. I'm not arguing against events. What I'm arguing against in this answer is indirection that is unnecessary. All events do in this case is get your code out of sequence. The repository is not waiting for something to happen; it's performing an operation on request. Events should be limited to triggering behavior that occurs at a time under the control of an external source (such as a user). A web request handler is essentially sequential: once the request is received, perform operations in order until a response is generated and returned.

        – jpmc26
        34 mins ago








      1




      1





      is it surprising that a repository with the primary role of persisting data to a database also sends e-mails - I think you missed the point of my question. My repository is not sending emails. It just raises an event, and how this event is handled - is the responsibility of external code.

      – Andre Borges
      1 hour ago





      is it surprising that a repository with the primary role of persisting data to a database also sends e-mails - I think you missed the point of my question. My repository is not sending emails. It just raises an event, and how this event is handled - is the responsibility of external code.

      – Andre Borges
      1 hour ago




      1




      1





      @AndreBorges My answer says, "The fact you delegate this out to an event makes the behavior even more surprising, since someone reading the code can no longer easily see what additional behaviors are invoked. Indirection harms readability." You're still thinking in terms of SRP. The whole point of my answer is that thinking that way is not helping you write better code. Good code makes most of what someone else needs to know about it obvious when they read it, and the way you've structured your code does not do so.

      – jpmc26
      1 hour ago






      @AndreBorges My answer says, "The fact you delegate this out to an event makes the behavior even more surprising, since someone reading the code can no longer easily see what additional behaviors are invoked. Indirection harms readability." You're still thinking in terms of SRP. The whole point of my answer is that thinking that way is not helping you write better code. Good code makes most of what someone else needs to know about it obvious when they read it, and the way you've structured your code does not do so.

      – jpmc26
      1 hour ago














      You're basically making the argument "don't use events."

      – Robert Harvey
      1 hour ago





      You're basically making the argument "don't use events."

      – Robert Harvey
      1 hour ago













      @RobertHarvey Yup. They make the connections between different parts of code much harder to discover, which makes understanding the sequence of events that occurs a much more difficult task. As I said, sometimes it's worth that price, but it's definitely a price you want to avoid paying whenever you can (which is usually in my experience).

      – jpmc26
      1 hour ago






      @RobertHarvey Yup. They make the connections between different parts of code much harder to discover, which makes understanding the sequence of events that occurs a much more difficult task. As I said, sometimes it's worth that price, but it's definitely a price you want to avoid paying whenever you can (which is usually in my experience).

      – jpmc26
      1 hour ago





      1




      1





      @RobertHarvey thinks Actually, I take it back. I'm not arguing against events. What I'm arguing against in this answer is indirection that is unnecessary. All events do in this case is get your code out of sequence. The repository is not waiting for something to happen; it's performing an operation on request. Events should be limited to triggering behavior that occurs at a time under the control of an external source (such as a user). A web request handler is essentially sequential: once the request is received, perform operations in order until a response is generated and returned.

      – jpmc26
      34 mins ago






      @RobertHarvey thinks Actually, I take it back. I'm not arguing against events. What I'm arguing against in this answer is indirection that is unnecessary. All events do in this case is get your code out of sequence. The repository is not waiting for something to happen; it's performing an operation on request. Events should be limited to triggering behavior that occurs at a time under the control of an external source (such as a user). A web request handler is essentially sequential: once the request is received, perform operations in order until a response is generated and returned.

      – jpmc26
      34 mins ago












      0














      Currently SaveChanges does two things: it saves the changes and logs that it does so. Now you want to add another thing to it: send email notifications.



      You had the clever idea to add an event to it, but this was criticised for violating the Single Responsibility Principle (SRP), without noticing that it had already been violated.



      To get a pure SRP solution, first trigger the event, then call all the hooks for that event, of which there are now three: saving, logging, and finally sending emails.



      Either you trigger the event first, or you have to add to SaveChanges. Your solution is a hybrid between the two. It doesn't address the existing violation while it does encourage preventing it from increasing beyond three things. Refactoring the existing code to comply with SRP might require more work than is strictly necessary. It's up to your project how far they want to take SRP.






      share|improve this answer



























        0














        Currently SaveChanges does two things: it saves the changes and logs that it does so. Now you want to add another thing to it: send email notifications.



        You had the clever idea to add an event to it, but this was criticised for violating the Single Responsibility Principle (SRP), without noticing that it had already been violated.



        To get a pure SRP solution, first trigger the event, then call all the hooks for that event, of which there are now three: saving, logging, and finally sending emails.



        Either you trigger the event first, or you have to add to SaveChanges. Your solution is a hybrid between the two. It doesn't address the existing violation while it does encourage preventing it from increasing beyond three things. Refactoring the existing code to comply with SRP might require more work than is strictly necessary. It's up to your project how far they want to take SRP.






        share|improve this answer

























          0












          0








          0







          Currently SaveChanges does two things: it saves the changes and logs that it does so. Now you want to add another thing to it: send email notifications.



          You had the clever idea to add an event to it, but this was criticised for violating the Single Responsibility Principle (SRP), without noticing that it had already been violated.



          To get a pure SRP solution, first trigger the event, then call all the hooks for that event, of which there are now three: saving, logging, and finally sending emails.



          Either you trigger the event first, or you have to add to SaveChanges. Your solution is a hybrid between the two. It doesn't address the existing violation while it does encourage preventing it from increasing beyond three things. Refactoring the existing code to comply with SRP might require more work than is strictly necessary. It's up to your project how far they want to take SRP.






          share|improve this answer













          Currently SaveChanges does two things: it saves the changes and logs that it does so. Now you want to add another thing to it: send email notifications.



          You had the clever idea to add an event to it, but this was criticised for violating the Single Responsibility Principle (SRP), without noticing that it had already been violated.



          To get a pure SRP solution, first trigger the event, then call all the hooks for that event, of which there are now three: saving, logging, and finally sending emails.



          Either you trigger the event first, or you have to add to SaveChanges. Your solution is a hybrid between the two. It doesn't address the existing violation while it does encourage preventing it from increasing beyond three things. Refactoring the existing code to comply with SRP might require more work than is strictly necessary. It's up to your project how far they want to take SRP.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered 15 hours ago









          CJ DennisCJ Dennis

          38717




          38717





















              0














              The code already violated the SRP -- the same class was responsible for communicating with the data context and logging.



              You just upgrade it to having 3 responsibilities.



              One way to strip things back to 1 responsibility would be to abstract the _userRepository; make it a command-broadcaster.



              It has a set of commands, plus a set of listeners. It gets commands, and broadcasts them to its listeners. Possibly those listeners are ordered, and maybe they can even say the command failed (which in turn is broadcast to listeners who had already been notified).



              Now, most of the commands may have only 1 listener (the data context). SaveChanges, prior to your changes, has 2 -- the data context, and then the logger.



              Your change then adds another listener to save changes, which is to raise new user created events in the event service.



              There are a few benefits to this. You can now remove, upgrade, or replicate the logging code without the rest of your code caring. You can add more triggers at the save changes for more things that need it.



              All of this gets decided when the _userRepository is created and wired up (or, maybe, those extra features get added/removed on the fly; being able to add/enhance logging while the application runs could be of use).






              share|improve this answer



























                0














                The code already violated the SRP -- the same class was responsible for communicating with the data context and logging.



                You just upgrade it to having 3 responsibilities.



                One way to strip things back to 1 responsibility would be to abstract the _userRepository; make it a command-broadcaster.



                It has a set of commands, plus a set of listeners. It gets commands, and broadcasts them to its listeners. Possibly those listeners are ordered, and maybe they can even say the command failed (which in turn is broadcast to listeners who had already been notified).



                Now, most of the commands may have only 1 listener (the data context). SaveChanges, prior to your changes, has 2 -- the data context, and then the logger.



                Your change then adds another listener to save changes, which is to raise new user created events in the event service.



                There are a few benefits to this. You can now remove, upgrade, or replicate the logging code without the rest of your code caring. You can add more triggers at the save changes for more things that need it.



                All of this gets decided when the _userRepository is created and wired up (or, maybe, those extra features get added/removed on the fly; being able to add/enhance logging while the application runs could be of use).






                share|improve this answer

























                  0












                  0








                  0







                  The code already violated the SRP -- the same class was responsible for communicating with the data context and logging.



                  You just upgrade it to having 3 responsibilities.



                  One way to strip things back to 1 responsibility would be to abstract the _userRepository; make it a command-broadcaster.



                  It has a set of commands, plus a set of listeners. It gets commands, and broadcasts them to its listeners. Possibly those listeners are ordered, and maybe they can even say the command failed (which in turn is broadcast to listeners who had already been notified).



                  Now, most of the commands may have only 1 listener (the data context). SaveChanges, prior to your changes, has 2 -- the data context, and then the logger.



                  Your change then adds another listener to save changes, which is to raise new user created events in the event service.



                  There are a few benefits to this. You can now remove, upgrade, or replicate the logging code without the rest of your code caring. You can add more triggers at the save changes for more things that need it.



                  All of this gets decided when the _userRepository is created and wired up (or, maybe, those extra features get added/removed on the fly; being able to add/enhance logging while the application runs could be of use).






                  share|improve this answer













                  The code already violated the SRP -- the same class was responsible for communicating with the data context and logging.



                  You just upgrade it to having 3 responsibilities.



                  One way to strip things back to 1 responsibility would be to abstract the _userRepository; make it a command-broadcaster.



                  It has a set of commands, plus a set of listeners. It gets commands, and broadcasts them to its listeners. Possibly those listeners are ordered, and maybe they can even say the command failed (which in turn is broadcast to listeners who had already been notified).



                  Now, most of the commands may have only 1 listener (the data context). SaveChanges, prior to your changes, has 2 -- the data context, and then the logger.



                  Your change then adds another listener to save changes, which is to raise new user created events in the event service.



                  There are a few benefits to this. You can now remove, upgrade, or replicate the logging code without the rest of your code caring. You can add more triggers at the save changes for more things that need it.



                  All of this gets decided when the _userRepository is created and wired up (or, maybe, those extra features get added/removed on the fly; being able to add/enhance logging while the application runs could be of use).







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered 1 hour ago









                  YakkYakk

                  1,869610




                  1,869610



























                      draft saved

                      draft discarded
















































                      Thanks for contributing an answer to Software Engineering Stack Exchange!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid


                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.

                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function ()
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fsoftwareengineering.stackexchange.com%2fquestions%2f389237%2fapplicability-of-single-responsibility-principle%23new-answer', 'question_page');

                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown











                      Popular posts from this blog

                      Romeo and Juliet ContentsCharactersSynopsisSourcesDate and textThemes and motifsCriticism and interpretationLegacyScene by sceneSee alsoNotes and referencesSourcesExternal linksNavigation menu"Consumer Price Index (estimate) 1800–"10.2307/28710160037-3222287101610.1093/res/II.5.31910.2307/45967845967810.2307/2869925286992510.1525/jams.1982.35.3.03a00050"Dada Masilo: South African dancer who breaks the rules"10.1093/res/os-XV.57.1610.2307/28680942868094"Sweet Sorrow: Mann-Korman's Romeo and Juliet Closes Sept. 5 at MN's Ordway"the original10.2307/45957745957710.1017/CCOL0521570476.009"Ram Leela box office collections hit massive Rs 100 crore, pulverises prediction"Archived"Broadway Revival of Romeo and Juliet, Starring Orlando Bloom and Condola Rashad, Will Close Dec. 8"Archived10.1075/jhp.7.1.04hon"Wherefore art thou, Romeo? To make us laugh at Navy Pier"the original10.1093/gmo/9781561592630.article.O006772"Ram-leela Review Roundup: Critics Hail Film as Best Adaptation of Romeo and Juliet"Archived10.2307/31946310047-77293194631"Romeo and Juliet get Twitter treatment""Juliet's Nurse by Lois Leveen""Romeo and Juliet: Orlando Bloom's Broadway Debut Released in Theaters for Valentine's Day"Archived"Romeo and Juliet Has No Balcony"10.1093/gmo/9781561592630.article.O00778110.2307/2867423286742310.1076/enst.82.2.115.959510.1080/00138380601042675"A plague o' both your houses: error in GCSE exam paper forces apology""Juliet of the Five O'Clock Shadow, and Other Wonders"10.2307/33912430027-4321339124310.2307/28487440038-7134284874410.2307/29123140149-661129123144728341M"Weekender Guide: Shakespeare on The Drive""balcony"UK public library membership"romeo"UK public library membership10.1017/CCOL9780521844291"Post-Zionist Critique on Israel and the Palestinians Part III: Popular Culture"10.2307/25379071533-86140377-919X2537907"Capulets and Montagues: UK exam board admit mixing names up in Romeo and Juliet paper"Istoria Novellamente Ritrovata di Due Nobili Amanti2027/mdp.390150822329610820-750X"GCSE exam error: Board accidentally rewrites Shakespeare"10.2307/29176390149-66112917639"Exam board apologises after error in English GCSE paper which confused characters in Shakespeare's Romeo and Juliet""From Mariotto and Ganozza to Romeo and Guilietta: Metamorphoses of a Renaissance Tale"10.2307/37323537323510.2307/2867455286745510.2307/28678912867891"10 Questions for Taylor Swift"10.2307/28680922868092"Haymarket Theatre""The Zeffirelli Way: Revealing Talk by Florentine Director""Michael Smuin: 1938-2007 / Prolific dance director had showy career"The Life and Art of Edwin BoothRomeo and JulietRomeo and JulietRomeo and JulietRomeo and JulietEasy Read Romeo and JulietRomeo and Julieteeecb12003684p(data)4099369-3n8211610759dbe00d-a9e2-41a3-b2c1-977dd692899302814385X313670221313670221

                      Creating closest line along the point''s azimuth using PostgreSQL Planned maintenance scheduled April 17/18, 2019 at 00:00UTC (8:00pm US/Eastern) Announcing the arrival of Valued Associate #679: Cesar Manara Unicorn Meta Zoo #1: Why another podcast?Drawing line between points at specific distance in PostGIS?How to efficiently find the closest point over the dateline?How to find the nearest point by using PostGIS function?PostGIS nearest point with LATERAL JOIN in PostgreSQL 9.3+Creating a table and inserting selected streets using plpgsql functionsCreating a table that stores Distances and other columnSaving select query results (year wise) from PostgreSQL/PostGIS to text filesWhat is the information behind this geometry?How to give start and end vertex ids dynamically in pgr_dijkstra?Point to Polygon nearest distance DS_distance is not using geography index & knn <-> or <#> does not give result in orderLine to point conversion with start point and end point detection?

                      Crop image to path created in TikZ? Announcing the arrival of Valued Associate #679: Cesar Manara Planned maintenance scheduled April 17/18, 2019 at 00:00UTC (8:00pm US/Eastern)Crop an inserted image?TikZ pictures does not appear in posterImage behind and beyond crop marks?Tikz picture as large as possible on A4 PageTransparency vs image compression dilemmaHow to crop background from image automatically?Image does not cropTikzexternal capturing crop marks when externalizing pgfplots?How to include image path that contains a dollar signCrop image with left size given