Moving IO to the edges of your app: Functional Core, Imperative Shell - Scott Wlaschin

แชร์
ฝัง
  • เผยแพร่เมื่อ 11 เม.ย. 2024
  • This talk was recorded at NDC London in London, England. #ndclondon #ndcconferences #developer #softwaredeveloper
    Attend the next NDC conference near you:
    ndcconferences.com
    ndclondon.com/
    Subscribe to our TH-cam channel and learn every day:
    / @NDC
    Follow our Social Media!
    / ndcconferences
    / ndc_conferences
    / ndc_conferences
    #functionalprogramming #architecture #code
    Modern architectures (such as Onion, Clean and Hexagonal) recommend that interfacing with the outside world be done at the boundaries of your app, not in the middle. Similarly, in functional programming, the core code should be deterministic, and all I/O should be at the edges.
    But how can you actually do this in practice? How can you separate I/O from business logic in an elegant way?
    In this talk, we'll look at some concrete examples of how to refactor code in this way. We'll also talk about how doing this improves code comprehension, testing, and refactoring.
  • วิทยาศาสตร์และเทคโนโลยี

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

  • @jamesarthurkimbell
    @jamesarthurkimbell หลายเดือนก่อน +8

    "Every function pure, except main()" is my little rule for myself. And while that'd surely have to change for large projects, it's a solid enough educational reminder.

  • @magne6049
    @magne6049 หลายเดือนก่อน +7

    in JS: use async functions for «Procedures» which handle IO imperatively, and synchronous functions for «Pure Functions» which handle business logic. That way, the «functioning coloring» (not being able to mix and match async and sync functions) will help you structure your code into a «functional core, imperative shell»!

  • @cathodion
    @cathodion หลายเดือนก่อน +5

    42:50 Another way of thinking about the fact that silver bullets don't exist is "there are no solutions, only compromises" because every different way of solving a problem is a balance between several important thing to consider. Like code brevity and complexity. Extremely compact code is usually more difficult to read and maintain, while code that is easy to read and maintain is usually not the shortest way of solving the problem.

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

      Maybe use an optimising compiler for the shortest way of solving the problem??

  • @benlevy1896
    @benlevy1896 หลายเดือนก่อน +4

    Dependency injection is isomorphic to a single DSL/interpreter for the whole class and dependency parameterization is isomorphic to having a unique command language per method.
    Another option for parameterization would be defining a single interface for every method and a concrete implementation can implement the intersection of them all. This would be equivalent to using an open union for your command language.
    Additionally, dependency rejection is merely a subset of dependency interpretation where the querys can be arranged to occur at the start and the commands can all occur at the end.
    Thus the final presentation of 5 options is really only 2, either you depend on implementations or interfaces.

  • @havokgames8297
    @havokgames8297 หลายเดือนก่อน +4

    00:26:00 Will be nice when C# has discriminated unions. The pure example would be that much nicer.
    00:41:00 The alternative that I've seen for splitting the workflow into multiple IO/pure code parts is to be a little less efficient and eagerly pull more data in the first I/O step than you may always need. It means that you sometimes query extra data when you don't actually need it during decision making, but makes your workflow simpler. It's a trade off to make a choice about.

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

      Pulling more data from a database may be an option to simplify the workflow. But when the IO in the middle is a web API call, it should not be done inconditionnaly because of performance and pricing.

    • @havokgames8297
      @havokgames8297 หลายเดือนก่อน +2

      @@blidi666 I think that’s exactly what I said: “It’s a trade off to make a choice about”. For most web applications the performance change will be negligible for improved code maintainability.

  • @eddiewouldnz
    @eddiewouldnz หลายเดือนก่อน +2

    Great talk, I've been pushing similar ideas myself on my blog.
    If you're using C#, small single method interfaces (with a linter rule) might be better than delegates / funcs (from a tooling perspective)
    You didn't mention it, but with imperium sandwich there is an important consideration - you'll often end up fetching more data than you need (because only the business logic knows what's truly needed).

  • @msrobinson42
    @msrobinson42 หลายเดือนก่อน +3

    Loved the talk!

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

    Brilliant and simple, as always.

  • @programmer1356
    @programmer1356 หลายเดือนก่อน +2

    Fantastic. I think you have got everything right.

  • @appology9102
    @appology9102 หลายเดือนก่อน +3

    Great talk. The reason I don't like parameter injection is because I have to pass the I/O funcs with each call. Constructor injection has the downside of potentially creating deps when calling functions that might not need them (i.e. get type from container with 5 deps just to call function that only uses 1). Perhaps that's when it makes sense to create a new class? Interpreter pattern looks interesting!

  • @detaaditya6237
    @detaaditya6237 หลายเดือนก่อน +5

    Man scott wlaschin's talk is always so fun and easy to digest! Looking forward to his dependency interpretation talk (is this gonna be a talk about free monad? Idk)

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

      he says it @56:10

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

    Great talk!

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

    45:38 and 55:45 ‘Dependency interpretation’ seems like ‘Structured programming’ like Effect systems like EffectJS and Haskell IO monad uses.

  • @luvincste
    @luvincste หลายเดือนก่อน +1

    last one was free monads, wasn't it? (yeah he said it, i didn't hear it at first)

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

    What about validation against biz logic, should we separate validating the input with validating the biz logic?

    • @havokgames8297
      @havokgames8297 หลายเดือนก่อน +3

      What do you mean "validation the business logic"? If you are receiving input somehow, like via HTTP etc, then validate it before entering the Pure code. If you can have the Pure code accept data that enforces its own invariants, then the Pure code doesn't need to worry about validation. If somehow the validation is related to the actual business logic, then it would be one of your 'decisions' that you return - some kind of "Couldnt process due to this reason decision", and your shell can deal with that by skipping IO and returning a response to where is needed.

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

    26:10 is it a pure function/implementation if it sends an email? surely that's IO? or is that just a simplification for the talk?

    • @JaconSamsta
      @JaconSamsta หลายเดือนก่อน +2

      It's not actually sending an email there. It is just passing the instruction to do so back to the caller.
      You can see at 27:40, where he shows the shell, that that is where the email and the database update is performed.

  • @VoroninPavel
    @VoroninPavel หลายเดือนก่อน +1

    What if subset of inputs affects what other data my domain needs for the flow? Should I preemptively read all such data and kill the performance?

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

      You can build multiple of these pipelines and string them together. That is perfectly fine.
      A result from such a function doesn't have to be limited to "UpdateUsername", but it could also be "RetreiveAditionalData", along with an object that represents the current state of the process, a checkpoint if you will. Then you can retrieve that data and pass it, together with your checkpoint object, into another pure function to complete the operation and produce the final result.
      But yes, pre-emptively reading all data might be a very valid option as well.
      Doing multiple disjoint IO-operations might cost you more in overhead, than just reading a bit more data in the first place. If you are writing this kind of software, you should be logging all of these operations so you can look back and make informed decisions on this kind of stuff. If 90% of the time you find yourself needing more data halfway through the process, then just reading that data regardless might be a worthwhile trade-off.
      You're probably going to hurt your best-case and your probably going to improve your worst-case with such an approach. How this ends up affecting your average is really going to depend. Again, you should be tracking those metrics in a real project, so you can make actual informed decisions.
      Generally, the point is that IO is a messy business. It can fail, it's hard to test, it produces exceptions or other error handling headaches. Clearly separating your IO from your domain and your business logic makes the business logic infinitely more testable and maintainable.
      Have requests for IO bubble up to the surface, handle those in the upper layers of your codebase where they are easy to audit and then pass the results back into pure functions.

    • @martinfreund3007
      @martinfreund3007 19 วันที่ผ่านมา

      Hello @VoroninPavel, i like to point you to 40:35 and following. I endorse Scott's explanation of intersecting I/O as i am working to move legacy WinForms projects into services. Segragating partial business logic and "pulling up" the I/O to the main loop is the only thing that works for me. If you collect all data up-front or intersect another DB query after processing the first user input is offen quite natural or just a matter of taste.

  • @zumalifeguard3493
    @zumalifeguard3493 14 วันที่ผ่านมา +1

    If you just followed this pattern, you'd have functional core, with an imperative shell that deals with IO. Now you have to do more functional stuff that you couldn't do before because it requires the result of the IO. So you do it again, you accept the IO from, do some pure stuff, and then result go to IO. And then again. Scott is completely ignoring how real world application s work by showing toy examples.
    The reason is because it gets way more complicated, and that makes this functional style a lot more complicated. You're not getting rid of complexity.
    Next: You never have to mock pure code. Hogwash. You mock those things that are slow. If I'm doing heavy math that takes 10 seconds to calculate, I'm not going to run that in every test simply because it's "pure". I'll mock it when I'm testing other code that depend on it.
    Scott knows this. He's been doing this long enough. But he loves to trivialize things to make thing seem more stable, and then pull out the "it depends" card later. It just comes across disingenuous.

    •  8 วันที่ผ่านมา

      Good comment. I also found the talk too simplistic. Most of the things that he has done would be done normally by good practices (function should do one thing).

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

    Great talk, however async void methods cannot be excused.

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

    16:45 CompareTwoStrings("a", "b"); // expect Bigger
    I will keep watching for now, but isn't that sort of the opposite of what we would expect?

  • @cocoscacao6102
    @cocoscacao6102 หลายเดือนก่อน +4

    Ehm... Whenever you don't use EF, you'll eventually reinvent it...

  • @vyrp
    @vyrp หลายเดือนก่อน +2

    Great talk! I agree with most of the ideas there. However, please, please, avoid `async void`. Use `async Task` instead.

    • @logank.70
      @logank.70 หลายเดือนก่อน +1

      It made it really distracting to me but I tried to focus on the content of the talk instead. Completely agree though. The use case of `async void` is primarily for event handlers. I might be wrong but if memory serves me the whole `async void` concept is allowed because of event handlers. Everything else should really be returning a `Task` or `Task`.

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

      I would be surprised if someone like him didn't know that.
      Maybe he did it like this to cement the idea that it's a function that doesn't return anything. We may know `Task` is equivalent to `void`, but newer devs might not know that and end up distracted from the actual point.

  • @patrik.0
    @patrik.0 หลายเดือนก่อน +8

    Should have used something else then string compare in first example, original code looks to similar as his refactored to really highlight the idea.

    • @chip243
      @chip243 หลายเดือนก่อน +1

      Agreed. It is the sort of example I would expect from someone trying to strawman this approach. The testability is actually reduced, because it doubles the number of conditional tests. Hopefully Scott comes up with a better example in future talks.

  • @alexandregravem6043
    @alexandregravem6043 หลายเดือนก่อน +2

    These are really silly examples. Very few domains are pure and very quickly you'll need indeterminism in the core. A secure password hashing algorithm is not pure, for example. Also for any real world scenario you'll need to compose workflows and things get very complex very quickly. I'm sure Scott knows about it. I wonder why he gives these talks making all look so simple when it's not.

    • @sfsdeniso5941
      @sfsdeniso5941 หลายเดือนก่อน +1

      80% of real real world examples are: receive http request, validate input data, save i it into db or receive http validate input read data from db, do data processing, save into DB, return result

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

      @@sfsdeniso5941 I'll give you a simple example where his model breaks. How do you check if the user's email is unique? You have to check before invoking the domain model. So his architecture pushes you to build an anemic domain model, which is bad design. What he presented here is a good starting point but it's not complete and I ask myself why not present the whole thing.

  • @black-snow
    @black-snow 22 วันที่ผ่านมา

    "An output with no input is bad design".
    *Reactive programmers typing hectically.*