Highly COHESIVE Software Design to tame Complexity

แชร์
ฝัง
  • เผยแพร่เมื่อ 21 พ.ย. 2024

ความคิดเห็น • 56

  • @CodeOpinion
    @CodeOpinion  3 ปีที่แล้ว +5

    Do you primarily think about cohesion in the sense of informational cohesion or functional cohesion?

    • @qizhang5749
      @qizhang5749 3 ปีที่แล้ว

      Primarily informational. I start with a model of the database tables and how they interact. Usually because I know what data I want and need but not how they would be used by different parties.

    • @alivateRocket
      @alivateRocket 3 ปีที่แล้ว

      Informational Cohesion, that's what is custom and specific. Within Data, informational cohesion is inescapable, all data is relational/graph. I believe it's possible to remove "implemented" informational cohesion completely from custom lines of code. You still need documentation and planning around it though of course.
      I understand "PostgreSQL Database Driver and SQL dialect" to be a kind of Functional Cohesion, an essential general tool that you need, just like the operating system, and the programming language - they are all generalised and flexible. I believe the number of Functional Cohesions should be minimised - you shouldn't have PostgreSQL, Redis, and ElasticJS - at least not to begin with on a smaller project - you should start by supporting one operating system, but with tools that can take you further if you need to.

    • @markuss.6981
      @markuss.6981 3 ปีที่แล้ว

      Informational cohesion is probably a starting point when creating a new feature as the tasks the feature provides will show up in a later stage when you are extending the feature. Often people just blindly follow the cohesion pattern that is already there from an earlier stage of the feature / software and just keep extending those services bounded by informational aspects. Constant refactoring towards achieving functional cohesion should be the goal imho.

  • @BertrandLeRoy
    @BertrandLeRoy 3 ปีที่แล้ว +15

    Every time you remove mocking code from a test, it’s likely you’ve improved your architecture. Very nice as usual!

  • @pawetarsaa9904
    @pawetarsaa9904 3 ปีที่แล้ว +5

    I have the impression that the problem you solved was just migrated to higher level. I believe there is still a need for this mock test but on the function that calls the handler since the implemenation of this delegate is still required

  • @hpdipto
    @hpdipto 3 ปีที่แล้ว +1

    "When I'm thinking about cohesion, I'm thinking about functional cohesion. Not informational cohesion." -- precisely clear!

  • @LeonardoDias-te2gg
    @LeonardoDias-te2gg 3 ปีที่แล้ว +6

    Great content!
    I always thought this idea of having a service per entity that does a lot of things was a bad idea. In your example, you have only a few methods, but I have seen "entity services" with a lot of methods and large methods. One thing I love in CQRS is that if I have a "Create Product Command" it only needs to change if the business rules for creating a new product change. But if I had a ProductService with lots of methods, this class/interface could have many reasons to change(for example if I change the rules for another method but the CreareProduct).
    The delegate approach you show in this video is very interesting, I'm gonna try to add it to my projects when I have the opportunity.

  • @keithnicholas
    @keithnicholas 3 ปีที่แล้ว +1

    I used to go on a lot about cohesion and coupling, and while they influence my thinking, I feel like they are derivative of things that compose well. Making things that compose well is much more interesting and drives cohesion and (de)coupling. It also influences knowing when cohesion and coupling don't really matter (ie, things that don't have to compose well with other things)

    • @CodeOpinion
      @CodeOpinion  3 ปีที่แล้ว

      I agree. I'm not always thinking about cohesion and coupling directly. It's more of a by product of how I organize and compose things together. Having said that, understanding or and being aware of cohesion and coupling gives the foundation, which was the intent of the video.

    • @TimSchraepen
      @TimSchraepen 3 ปีที่แล้ว

      Imho: Coupling and cohesion are heuristics one can use to decide the degree of how composable a thing should be. They’re not mutually exclusive concepts.

  • @Akhbash
    @Akhbash ปีที่แล้ว +1

    What was the difference between the two implementations showed in this video? Why the last test should've passed?

  • @alivateRocket
    @alivateRocket 3 ปีที่แล้ว +3

    The problem with uncle Bob's definition is that it doesn't describe the cohesion boundary. Cohesion is about what is controlled from within the boundary.
    If you make the boundary too small, control is outside that boundary, if you make the boundary too large everything fits inside, but it's now the whole system.
    Discussion about the boundary line is what uncle Bob and many others talk about, that's the process of sizing a module, but that isn't the definition of cohesion.
    You need to iterate through different boundary options where cohesion is optimised and module size is minimised.
    So cohesion itself is not the size, it's the criteria for internal control of resources (and dependencies). I will be writing some much clearer articles about this, this year.

    • @CodeOpinion
      @CodeOpinion  3 ปีที่แล้ว

      Agree about boundaries. A lot of the videos I post are about or revolve around boundaries and defining them. Specifically focusing on behaviors as the primary driver. So in isolation this video and talking about SRP may not seem entirely clear but I hope those that watch more will get a better picture.

    • @alivateRocket
      @alivateRocket 3 ปีที่แล้ว

      @@CodeOpinion For boundaries I find that "focusing on behaviors" becomes too obscure, and avoids giving the idea of cohesion and coupling a clear definition. I am looking for measurable properties about the boundary itself. For example, breaking down the practical minimal components that a modular system needs - connection to data, control over authorisation, connection to other modules, etc... where are those configured and controlled? Often they are configured and controlled outside of the boundary. I am working on this kind of thinking and I plan to publish quite thorough research in the near future when it's coherent and polished.
      The factors of "behaviours" are certainly important, but the idea of a "cohesion boundary" needs to be able to stand apart. You certainly bring context to the practice of finding the boundary - context is everything. But then it's good to be able to analyse the outcome objectively with some absolutes. Too many people out there say, "and therefore there's less coupling" - like it's some sort of emotion.

  • @francescodente2876
    @francescodente2876 3 ปีที่แล้ว +1

    I do agree with all you say in the video, but sometimes I tend to group behaviors based on the likelihood of them having to change together, even though they end up not being used together

    • @CodeOpinion
      @CodeOpinion  3 ปีที่แล้ว

      They may be used apart of the same workflow I assume, not necessarily the same unit of code (class/method/function). Which is still in-line with what I'm saying. The point is that behaviors are the leading driver. Those behaviors are what require data.

  • @hexdump8590
    @hexdump8590 3 ปีที่แล้ว +3

    Hi Derek! Again another eye opener video. I like the idea of functional cohesion. It fits and reinforces the idea of the vertical slice architecture where every slice is a high cohesive unit that goes hand to hand with the bounding context concept. But I have a question, the way you split entities by "function" can't lead to duplicate fields in different bounding contexts that will have to be kept in sync? Isn't this something hard to do? I mean if you have the warehouse bounding context and the sales bounding context that both have their vision of the product entity and now someone notice that the sku is wrong and needs to be changed (I just picked the field common in your example, can be any other that is shared) how would you go to solve this issue? It can't be changed in one place, it needs to be changed in every different "Product entity" owned by every bounding context. Please could you comment a bit about it? Thanks!

    • @CodeOpinion
      @CodeOpinion  3 ปีที่แล้ว +2

      Data is going to be owned by a service. If you do replicate it to other services, especially identifiers, I'd be surprised those actually change. But if you do have replicated data, just be aware that it could be stale and how you want to manage keeping it up-to date. I'd say most use either event carried state transfer or use events as notifications to then call the publisher. Check out this video: th-cam.com/video/qKD2YUTJAXM/w-d-xo.html

  • @DevCastOficial
    @DevCastOficial 6 หลายเดือนก่อน

    Thanks a lot for sharing knowledge!

  • @Byku010
    @Byku010 3 ปีที่แล้ว

    You have done such a great job at explaining things. Really good job dude! Keep it up and greetings from Poland!

    • @CodeOpinion
      @CodeOpinion  3 ปีที่แล้ว

      Thanks! Glad you like them

  • @AlexMercer00
    @AlexMercer00 3 ปีที่แล้ว +1

    How does DI work for the delegate and the method implementation?

    • @CodeOpinion
      @CodeOpinion  3 ปีที่แล้ว +2

      Register the delegate to a static method that meets the delegate signature. Or use the factory register to resolve a class and pass the method that meets the delegate signature.

  • @MrAyuub22
    @MrAyuub22 3 ปีที่แล้ว +1

    Haven't really heard much about delegates in .net core c#. Do you have a video on this?

    • @CodeOpinion
      @CodeOpinion  3 ปีที่แล้ว +3

      I don't have anything specific on them I think they are widely underutilized in application code. Func and Action are delegates that are more often in libraries and frameworks.

    • @andrewthompson9714
      @andrewthompson9714 2 ปีที่แล้ว

      @@CodeOpinion I wouldnt know how to write c# without action and func, but especially func.
      There is value of defining your own delegate types to be more domain specific too

  • @m1dway
    @m1dway 3 ปีที่แล้ว

    Should all the products under sales, warehouse and purchasing needs product name as well?

    • @CodeOpinion
      @CodeOpinion  3 ปีที่แล้ว

      Generally no. Not unless they need it for query purposes, which they could have a local cache copy of if they needed it. I wouldn't think they need it for invariants/business rules.

  • @JRTrue86
    @JRTrue86 3 ปีที่แล้ว +1

    Thanks for making great videos. I agree with what you're saying about cohesion. However, I don't agree with the delegate solution you proposed in this video. Generally, there's nothing wrong with delegates, they're very useful!
    By your own definition, in the video, a class should only do one thing. The product service does 4 things. Updates product info, gets by SKU, gets for sale by SKU, and gets all. I don't think that's a bad product service, I think it's fine. BUT, if you wanted to hide the implementation details you would inject an interface for EACH of those functions. IProductInfoUpdater, IProductSkuGetter, etc. (Forgive the shitty names, sorry.) Maybe CQRS?
    Basically, I don't think you should use delegates for this particular example, in fact I'd say it's a bad idea, but I agree with the principal conclusions and I enjoy your videos. Thanks.

    • @AlexMercer00
      @AlexMercer00 3 ปีที่แล้ว +1

      I disagree. If you are gonna break apart the functions away from the ProductService then a delegate for each would fit more than an interface. You use interface to group behaviors, but if your interface only has 1 method each then delegate is perfect choice. Delegates are best seen as Anonymous one-method interface.

    • @CodeOpinion
      @CodeOpinion  3 ปีที่แล้ว +1

      Thanks for the comment! I appreciate it. I can see the idea of multiple interfaces, however I'm not sure how an interface with one method is more useful than a delegate. The delegate is more explicit about the dependency in my view.

    • @JRTrue86
      @JRTrue86 3 ปีที่แล้ว +2

      @@AlexMercer00 Thanks, always interested in other opinions

    • @JRTrue86
      @JRTrue86 3 ปีที่แล้ว +1

      @@CodeOpinion Thanks for the reply. Your point about single function interfaces is fair. I don't think it's a huge deal here. I just don't want new developers littering their code with delegates, which can be difficult to debug (sometimes).

  • @paragraut3504
    @paragraut3504 3 ปีที่แล้ว

    Amazing and easy explanation

    • @CodeOpinion
      @CodeOpinion  3 ปีที่แล้ว +1

      Glad you think so!

  • @chucaraDK
    @chucaraDK 3 ปีที่แล้ว +1

    I maybe missing the point, but I'm not really sure what the gain is here. You are substituting an interface dependency (IProductService) for a static class dependency (ProductDelegates). I don't see how that changes cohesion. I prefer the inline mocking code in your case, but you lose the ability to use things like .Verify(..., Times.Once) from Moq.

    • @CodeOpinion
      @CodeOpinion  3 ปีที่แล้ว

      Taking a dependency on an interface where you require one method, and none of the methods relate to workflow, means you're likely going to need to use a limited number of methods on that interface. If that's the case, you don't want the interface, you want a function. The interface isn't a collection a collection of cohesive behaviors but rather a grouping based on data. The benefit of taking the dependency on the function/delegate is because the function is explicit. It defines its inputs and outputs which you can stub. You can stub an interface, but it's not as explicit about what your usage actually is of the interface. Again if the interface has 6 methods and you're usage is only 1, then if your usage changes, so does your test. This is the same with using a delegate but if the delegate dependency changes, you won't even be able to build.
      If you find value in using mocks and are concerned with if you're code under test is actually calling dependencies (eg using Verify), then keep doing what you're doing. I prefer to use stubs in their smallest form (delegates) instead of mocks.

    • @chadbennett
      @chadbennett 2 ปีที่แล้ว

      @@CodeOpinion - Does this mean the main driver for choosing delegates over interfaces is testing maintainability/simplicity? I feel like there are more reasons, but I am not sure I understand them. I get the point of an interface being one function and a delegate being a collection of functions, but that alone seems pretty trivial.

  • @k3nnydust
    @k3nnydust 3 ปีที่แล้ว

    how do you maintain correct sync between systems in a microservices environment? especially if data is common data is shared between them?

    • @CodeOpinion
      @CodeOpinion  3 ปีที่แล้ว

      My question would be why do need the data across services? If it's no it to enforce invariants and is solely for view/UI proposes, then that's a different topic 😊

    • @alivateRocket
      @alivateRocket 3 ปีที่แล้ว +1

      I'm not a huge fan of ideological microservices, but they are defined as "owning" data. That doesn't have to mean that they need to "own" the data infrastructure and manage the security, in fact, as you elude to, that can cause problems.
      You can have an ideological microservice architecture if you use a single database system, like PostgreSQL, and use the authorisation tools to limit access to individual microservices. Then if you want to orchestrate data, you can give such a microservice "read-only" access to a wide range of data, so you can JOIN. You can also let your customers loose with something like PowerBI with full read rights across the database.

  • @bennet7817
    @bennet7817 ปีที่แล้ว

    Nice

  • @WorthyVII
    @WorthyVII 3 ปีที่แล้ว +1

    Wait I don't understand, why is the test passing at the end a good thing?

    • @CodeOpinion
      @CodeOpinion  3 ปีที่แล้ว +3

      The signature of the dependency was the same. The stub ultimately is the same. If it wasn't it wouldn't build.

    • @WorthyVII
      @WorthyVII 3 ปีที่แล้ว

      ​@@CodeOpinion Ah, because its unrelated right. Ok, I thought I missed something different, thanks!

    • @TimSchraepen
      @TimSchraepen 3 ปีที่แล้ว +3

      Because coupling your test code too tightly to production code makes you change your tests everytime you refactor your production code. This sometimes constrains developers from refactoring or makes them spend a lot of unnecessary time when they want to add features.
      The test still verifies the same intent, but it does so in a way that’s more flexible for future development.

  • @bernhardkrickl5197
    @bernhardkrickl5197 ปีที่แล้ว

    I lol'ed because in your de-coupled products you can only ever *increase* price and cost. You can't decrease it. Commentary about our economic system hidden here ;)

  • @andrewthompson9714
    @andrewthompson9714 2 ปีที่แล้ว

    Never depend on things you don't need. It sends coupling through the roof and confuses the domain responsibilities

  • @Evgeniy994
    @Evgeniy994 2 ปีที่แล้ว

    Thanks for this video! I like the idea of taking the dependency on the delegate, however, it's not very clear to me how to test the delegate's implementation. Say, I have something like services.AddScoped(x => { ... }); How do I test this? Thanks.

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว

      Provide an implementation of the delegate for test, as shown in the later part of the video. If you're using the service provider apart of your test, then you'd have to register (add) the implementation.

  • @nicholas1460
    @nicholas1460 3 ปีที่แล้ว

    I think you have jumbled together the concept of views with entities and database (de)normalization. None the less, it's a good point that software needs to consider functional and domain aspects.

  • @ya4eburashka
    @ya4eburashka 3 ปีที่แล้ว

    Well, that code example part seems strange to me. First of all, you have 3 get* methods, but only one is actually used. I'd say, you either don't need that methods in the first place, or you will need them in your handler eventually. Keeping in mind this is made up expample, in real life that handler will (!) need more methods from service after some time. My second thought is that you still need a real implementation for the delegates, which has to be placed somewhere - you've said it in the video, but did not showed it. So the service stays the same, but now we should be searching for the implementation in our project, which slows debugging. Third thought, testing should not affect developing. Those two methods happened to have the same input and output, but in real life you'd need to change that test. After all, changing tests when we change logic is ok, so this approach have no benefits compared to mocking (where we'd need to change mocked method). But mocking have benefits in terms of clarity. With mocking, when I read a test, I can easily see what's happening. I can see, that, if service returned that object, result of this hadler should be that. And I can easily find tests in project for that method. "Delegate version" of this test tells me much less information. If some random method returns some product, it's somehow will be returned from Handle method. What this test actually testing? Delegate invoking? I hope Microsoft done it for me. And your example shows exactly that. Test did not reflect changes in code, so you're testing only a fact that injected delegate got invoked, whatever delegate it may be. I'm not sure that is something I want to test. After all, now the same problem could appear instead in injection logic - we set up a wrong service's method for this delegate and now our tests did not told us about it which defeats whole it's purpose.

    • @CodeOpinion
      @CodeOpinion  3 ปีที่แล้ว +3

      Thanks for the comment, let me try address some of the points you made.
      1 - The 3 get methods were to illustrate other methods on the interface that are likely used in other places. The point being is that you're injecting a dependency on an interface where you only need 1 of the methods. And this is because that interface is grouped by informational cohesion.
      2 - I didn't show the implementation or the interface or the delegate because it wasn't useful the point I was making. You're depending on an abstraction not an implementation.
      3 - Yes, in real life, if the dependency on the delegate changed, and it had a different signature, you'd actually wouldn't be able to build. Which means you're getting feedback quicker then running the test and it failing because you failed to mock a method on the interface. You mentioned in the test you can easily see what's being mocked. The same is with the delegate stub implementation. If you're SUT is a black box, and it takes a interface as a dependency, which methods on the interface need to be mocked? You don't know. The delegate is explicit on exactly what the dependency is. It's the exact opposite of "tells me much less information". It tells you more by specifying exactly in the smallest form possible.
      4 - Your proving a dependency to your SUT which has an expectation. You're providing that. That doesn't change if your creating a mock or a stub, they are both providing a return value for the SUT to execute. If the SUT stops using that dependency but still accept its, nothing changes. You're not testing that the delegate was called.
      Hope this answers some of the questions.