Moving IO to the edges of your app: Functional Core, Imperative Shell - Scott Wlaschin
NDC Conferences2024-04-12
NDC#Conferences#2024#Live#Fun#London#IO#Functional Programming#architecture#code#Scott Wlaschin
1K views|3 months ago
💫 Short Summary
The video emphasizes the importance of separating IO from domain logic to improve code readability and maintainability. It advocates for a domain-centric approach, good design principles, and functional programming. Testing concepts, unit of work, dependency injection, and parameterization are discussed for efficient software development. The video also addresses the significance of passing only necessary parameters, avoiding unnecessary complexity, and avoiding impure code in the core domain. The use of microservices for optimizing API calls and handling I/O dependencies is highlighted.
✨ Highlights
📊 Transcript
✦
Importance of separating IO from domain logic in software development.
01:32Keeping IO separate improves code readability and maintainability by making it easier to understand and test.
IO can be non-deterministic and prone to failure, making it necessary to avoid it in code or keep it separate from domain logic.
Shift from layered architectures to vertical slices in software design reduces code changes across multiple layers for each update or addition.
✦
Importance of code structure and architecture in software development.
04:30Advocates for a domain-centric approach over a database-centric one.
Emphasizes the need for core domain logic at the center of code surrounded by infrastructure like databases and networks.
Highlights the standard pattern of business applications involving data loading, processing, decision-making, and saving.
Introduces a function for adding numbers as an example of good design, emphasizing clarity of inputs and outputs.
✦
Importance of designing testable code in software development.
09:24Good design requires explicit inputs and outputs, deterministic behavior, and no side effects.
Inputs and outputs should be clear, consistent, and predictable in well-designed code.
Avoid reliance on magic numbers or random generators in code.
Emphasis on creating code that is easy to test and understand for efficient software development.
✦
Importance of functional programming in creating pure, deterministic code.
10:21Functions should have explicit inputs and outputs for predictability and testability.
The distinction between business logic and IO operations is crucial for maintaining deterministic code.
Keeping IO operations at the edges of the codebase is recommended.
Refactoring to separate deterministic business logic from non-deterministic IO is advised for improved code quality and maintainability.
✦
The importance of separating decision-making logic from IO operations in code design.
13:26The discussed function violated guidelines by lacking input or output, making it untestable.
The code mixed IO operations with decision-making logic, leading to confusion and inefficiency.
The proposed solution involved creating a special data type for decisions and passing explicit inputs to the function.
By following these guidelines, the function was redesigned with two inputs and one output, meeting functional code design criteria.
✦
Separation of business logic from input/output (IO) for easier testing.
16:59By separating IO from business logic, flexibility is increased in terms of data sources.
Switch between different data sources by abstracting out differences using inheritance and creating subclasses for each data source.
Narrator suggests that this approach may be overly complicated and unnecessary.
Emphasizes that a simpler solution may suffice.
✦
Benefits of separating business logic in coding.
20:41Separating business logic allows for improved efficiency in coding by easily switching between different implementations without complex interfaces.
Emphasis on composition over inheritance for simplicity and straightforwardness in coding.
Example provided on handling data updates on a website backend efficiently by retrieving existing data and updating the database only when necessary.
✦
Updating customer database and sending email notifications process.
23:12The segment explains steps for updating customer information, checking for changes, and sending emails based on updates.
Demonstrates code implementation for efficient handling of tasks.
Creating decision data structure with options like updating customer, sending emails, or both for organization and clarity.
Focus on keeping code pure and efficient by minimizing database hits and returning appropriate result types based on changes detected.
✦
Benefits of using deterministic and pure code and discriminated unions in F#.
27:42Deterministic and pure code makes testing easier and improves self-documentation.
Refactoring code simplifies testing and enhances code clarity.
Discriminated unions in F# allow for extra data associated with enum choices, improving domain modeling efficiency.
Using F# and discriminated unions is recommended for better code organization and modeling.
✦
The segment highlights the creation of a special type for decision-making and the separation of code into pure and impure sections.
30:36Pure code is characterized by explicit inputs and outputs, making it easier to test and lacking async and exception handling.
Impure code, on the other hand, deals with external factors and requires exception handling.
The presenter demonstrates the differences between pure and impure code, stressing the importance of structuring code for effective decision-making and testing.
✦
Importance of Testing Concepts in Software Development.
34:56Testing should focus on business value, stories, and use cases rather than individual classes.
Testing boundaries is crucial, and private methods should be avoided in testing.
Unit testing should be isolated and run separately, with fast execution and no involvement of IO operations for efficiency.
Integration testing is different from unit testing, emphasizing the need for clear separation and understanding of their purposes.
✦
Importance of separating validation from business logic in software development.
37:06Validation should occur before data is sent to business logic to maintain clarity and organization.
Defensive programming and null checking should be done at the beginning, rather than in the middle, of the domain.
ORMs like Entity Framework and Hibernate may not be ideal for this approach as they can lead to poorly designed methods within domain logic.
Placing I/O operations in the middle of logic can complicate testing and make the code less adaptable to changing business rules.
✦
Discussion on the concept of unit of work in database transactions.
39:17Limitations of the unit of work approach, including being database-centric and difficult for testing.
Recommendation for a design with a pure core and an imperative shell, using simpler data access methods like Dapper.
Emphasis on breaking workflows into testable, pure or IO pieces for efficient testing.
Suggestions for efficient code review practices to ensure good design principles are followed.
✦
Importance of Refactoring Code for Control Flow Patterns.
42:31Using exceptions for control flow is discouraged, signaling the need for refactoring.
Avoiding a one-size-fits-all solution in coding is crucial, with experimentation of different approaches recommended.
Managing IO dependencies involves strategies like dependency rejection, retention, and injection.
Emphasizing the importance of pure, deterministic code in domain modeling and advising against unnecessary abstractions like interfaces.
✦
Discussion on Dependency Injection and Parameterization in coding.
46:15Dependency injection is recommended for better code comprehension and ease of testing.
Emphasizes the importance of making code more understandable to reduce time spent on deciphering it.
Dependency retention involves hardcoding dependencies and is suitable for exploratory coding or throwaway scripts.
Explains the distinction between dependency injection and parameterization, with the former passing all dependencies to a class and the latter passing only necessary dependencies for a specific function.
✦
Discussion on the use of interfaces in coding for database operations and email services.
49:04Demonstrates injecting local fields into constructors and the significance of passing required parameters to methods.
Highlights the potential challenges of interface creep, where interfaces are overloaded with unnecessary functions.
Emphasizes the benefits of passing only essential parameters to methods for improved code organization and efficiency.
Compares constructor injection with parameterized implementation to showcase the advantages of the latter.
✦
Importance of passing in only necessary parameters for methods.
52:31Passing in unnecessary parameters can lead to errors and bloated methods.
Discussion on interface creep and caution against adding unnecessary methods to interfaces.
Significance of interface segregation principle to prevent interface creep and maintain clean code.
Advantages of dependency injection and reminder to avoid adding unnecessary complexity to interfaces.
✦
Importance of good design in software development.
54:51Laziness should lead to doing the right thing.
Parameterization is favored over interface creep to reduce dependencies.
Challenges of working with deeply nested code and limitations of frameworks in supporting dependency injection.
Introduction of dependency interpretation for domain-specific APIs and powerful software development tool.
✦
Use of microservices by Twitter and Facebook for optimizing API calls.
58:49Merging database updates into batch updates can streamline operations and reduce API calls.
Importance of avoiding impure code in the core domain and handling I/O dependencies through techniques like dependency injection.
Mention of the Interpreter pattern as a complex solution for specific cases but likely unnecessary for most users.
✦
Speaker expresses gratitude for support received for their book.
01:00:23Speaker finds it enjoyable that people like the book.
00:06so um I'm Scott velan uh I have a
00:09website called f fund profit which is
00:11about F most examples in this talk will
00:14actually be C because I don't want to
00:16put you off too much um I will post the
00:19slides and the code I'm using on my
00:21website at iio edges it's not there yet
00:25um but in the next week or so it should
00:26be posted up there so okay I'm going to
00:29talk about two things things first of
00:30all I'm going to talk about how to avoid
00:32IO in your core
00:34domain uh and this is often called
00:36functional core imperative
00:39shell and then um for the last few
00:42minutes I'll talk about other ways of
00:44working with the io in terms of managing
00:46dependencies there's more ways to manage
00:48them than using sort of dependency
00:50injection there are some couple other
00:51ways so I'll just quickly take you
00:53through those as well if you don't like
00:54the main way of doing it five different
00:56ways in fact okay so why IO why am I
01:01talking about IO so much um if you care
01:04about modeling your domain nicely if
01:08you're doing domain with design or
01:09you're doing anything where you want
01:10your code to look nice IO is not part of
01:13that you know whether you save something
01:15to a database or a file system or a
01:17cloud storage whatever that's not part
01:19of your business logic right it some
01:22maybe sometimes is but almost always it
01:23isn't it's not not part of your domain
01:25so if you mix your IO in with your
01:27domain logic it's harder to understand
01:30what's going on you just intermingled so
01:32it's not good um iio is
01:34non-deterministic right that means every
01:37time you call an IO thing you might get
01:40different results you read a record from
01:42a database who knows what you're going
01:43to get back you read something from the
01:45network you read something from file
01:46system is very unpredictable so it's
01:48very hard to
01:49test and also IO can
01:53fail right I mean it might crash you
01:55might throw an exception so if you're
01:57going to have IO in your code you you
02:00have to handle exceptions to you know to
02:01have it production ready uh code so
02:04that's annoying so since IO is so bad my
02:08recommendation is actually to avoid all
02:10IO in your code
02:12whatsoever so that's that would be a
02:14really good design guideline um of
02:17course you know it's not actually
02:18possible to do this because at some
02:19point you need to write to the database
02:21or whatever so you can't actually do it
02:24but if you pretend that you can if you
02:26try and do it as much as possible I
02:27think that's a good design principle
02:30so rather than saying avoid all IO I'm
02:33just going to say keep the I IO separate
02:36from the domain logic keep it away from
02:37the domain logic and that is possible
02:40and I'll show you how to do
02:42that now if you look at a additional
02:45enter layered architecture that was very
02:49common up until a few years ago um you
02:52had this kind of layered thing and you
02:53start with the API the presentation
02:55layer and then it goes to the services
02:56or the application and then the domain
02:58the business logic and you always have
03:00the database layer at the bottom right
03:03so the io is at the bottom it's the most
03:05important thing because back in the day
03:07databases were the absolute most
03:09important thing so this this style is
03:13kind of going out of fashion and one
03:15thing that's replacing it is vertical
03:18slices and one of the reasons is that in
03:20this model every time you change
03:22something you have to change something
03:23in four places if you add a new field to
03:26your customer you have to change the
03:28Json and you have to change the database
03:29and you have to change them you know
03:31it's very complicated and and one of the
03:33rules is one of the good guidelines is
03:35code that changes together should be you
03:38know live together right code that lives
03:40together should change together and vice
03:41versa so if you put them in slices like
03:43this that's much better because if you
03:45change one thing you're not affecting
03:47all the other things so this is good and
03:49I think there was a talk on this earlier
03:50today actually but from my point of view
03:54you still have IO at the bottom now if
03:56you kind of disentangle this thing if
03:57you stretch it out horizontally you end
04:00up with something looking like this
04:02where something some data comes in and
04:04it goes through the API or the
04:06presentation layer whatever and in the
04:08middle is the database still so this is
04:11a database Centric
04:14architecture right so I'm going to say
04:17that I prefer not to do that I like
04:19domain driven design and I like putting
04:22the domain at the middle of my code and
04:25not the
04:26database so let's not do this let's do
04:28this instead so this is what a domain
04:30Centric architecture looks like you have
04:33your core domain all your business logic
04:35and all your rules and everything you
04:37need to do to actually the logic of what
04:39you're doing and then on the outside is
04:41all the infrastructure the databases the
04:44network the file system all that stuff
04:46is around the
04:48outside so if you have a particular
04:50workflow or a story or a use case I'm
04:53going to use workflow to mean story use
04:55case something like that so you have a
04:56particular workflow and it goes it cuts
04:59kind of cuts through it comes in from
05:00the
05:01outside uh through the infrastructure
05:03layer and then it goes into the the core
05:06domain logic then it comes out the other
05:08side so if you take that workflow and
05:10you look at it it's going to look like
05:13this right so all the io is at the edges
05:16so again in this model iio is at the
05:18edges that's good we take one slice and
05:22we have something that looks like this
05:23you do some IO then you do your business
05:26logic and then you do some more io on
05:28the way out
05:30so that's that's exactly the point of
05:32this talk all the iio is at the edges so
05:34this is great this is exactly what we
05:36want to
05:37do and typically I would say that most
05:41business applications certainly have a
05:44very standard pattern you load some data
05:45up at the beginning of the workflow you
05:47process it you make some decisions you
05:49decide what to do and then you save some
05:51data back at the end I mean I would say
05:53I mean not always like this but I say
05:56most business applications like this
05:57this is kind of the classic crud style
06:00thing and it it's fine so you know this
06:02will fit for most things you're
06:06doing right so there we go now so okay
06:08that's the plan that's the theory how do
06:11you actually do it in
06:13practice so before we do that before we
06:15get some guidelines for what's good and
06:17bad design um I'm actually going to look
06:20at some good and bad design and you can
06:22come up with the guidelines yourself so
06:24here is a function which adds two
06:26numbers right it's called addition and
06:28there are two input and one
06:31output so my first question is it
06:35understandable how to use this function
06:37without actually looking inside the
06:39Black Box given that I call it addition
06:42and it's got two inputs and one output I
06:44mean you could probably guess how you
06:45know you put in two numbers and you get
06:46another number out right so I would I
06:48would it's pretty understandable
06:50comprehensible just by looking at the
06:52inputs and outputs you can kind of get a
06:54good idea of what's going on and is it
06:56testable I wanted to I wanted to test
06:58this piece of code how would I test it
07:00well you know I pass it you know two and
07:03three and I hope to get five out the
07:05other end right that would be a very
07:07easy way of testing this so you know you
07:09give the inputs and you expect the right
07:11output very testable that's very good so
07:14I think this is a good design this is
07:15the kind of design that we
07:17want let's look at this one okay this is
07:20another function called
07:22addition and there's no input and
07:25there's no
07:26output I mean it's like okay
07:30so how would you even call this
07:32function how would you even use it and
07:35it's like well I don't know cuz like
07:36where it's it's adding numbers where
07:38these numbers coming from and is it
07:40testable how would you test a function
07:42like this there's no input there's no
07:43output how can you even set expect you
07:46know it's like no you can't even test it
07:49so this is what I would call a bad
07:51design okay there's no input and no
07:53output that's really
07:55bad so and you think well who would even
07:58write a function with no input and no
08:00output and the answer is we all do and
08:03I'm going to show you an example very
08:04shortly of a function I've written which
08:05looks just like this so you know when
08:08you see the magic inputs are coming
08:10they're like magically appearing out of
08:12nowhere it's kind of fun but it's really
08:15hard to understand what's going on so
08:16this I think this is anything like this
08:17is bad bad
08:20design okay here's another mystery code
08:23function and there's an input which is
08:25good but there's no
08:27output right so how how would you test
08:30this how would you how do you use this
08:32again it's not really clear what it's
08:34doing I mean how can how can you do
08:36something and there's no output what
08:37does that even mean so it looks like
08:39it's not doing anything it's really hard
08:41to test because there's no output to
08:43check the results of so this is a bad
08:46design as well okay and and lastly let's
08:49look at the other way around where you
08:50do have an output but there's no input
08:53so where is the output coming from it's
08:55just like making stuff
08:57up so again I don't even know know what
09:00it's doing I don't know how to work with
09:01it I don't know how to test it because I
09:03can't control the input it just give me
09:06in output that I don't even know where
09:07it's coming from so this is a bad design
09:09too so we've got one good design and
09:12three bad designs and the good design is
09:16the one we want to do and so let's look
09:18at the guidelines for a good design so
09:22to make it
09:24comprehensible we want everything to be
09:26explicit right we want all the inputs to
09:28be explicit and the output to be
09:30explicit we don't want any magic numbers
09:33coming from somewhere we don't even know
09:34where they're coming from we we want to
09:36have input parameters where it's like
09:38okay I pass in this and I pass in this
09:40and I get this output it just doesn't
09:42you know that it just doesn't disappear
09:43right so that's good we also wanted to
09:46be deterministic meaning if I give you
09:49you know two and three I expect to get
09:51five no if I always give you the same
09:53inputs I always expect to get the same
09:55outputs I mean that's kind of common
09:57sense but you know you don't want to
09:59random number generator in there like
10:01generating random numbers right that's
10:02not good so deterministic is good and
10:05also having no side effects is good
10:07meaning it only does that one thing it
10:10adds two numbers together it doesn't
10:11launch missiles it doesn't delete the
10:12database all that stuff right it just
10:14does that one thing really well so those
10:17last two things being deterministic and
10:19not having side effects is what
10:21functional programs call Pure code so
10:23I'm going to use that word from now on
10:25pure and um basically I'm going to use
10:28pure to mean all of these things where
10:29all the inputs are provided uh and it's
10:32deterministic and it just does what it
10:34says on the tin it doesn't do anything
10:35else so this is what we want to have
10:37happen right this is good if we follow
10:39this we should get good code now here's
10:42the problem IO does not meet these
10:46guidelines so we talked about functions
10:49that have an input but the output
10:51there's no output right well you know
10:53writing to the file or writing to the
10:55database updating a database I mean yeah
10:58they might have the number of Byes
10:59written or something it's not very
11:00useful output you know basically when
11:02you write something to the file or to a
11:05database you basically get no useful
11:07stuff back it just kind of
11:09disappears right so these are are bad
11:12design they don't follow our guidelines
11:13so from my point of view these are bad
11:15things to have in your code now again
11:17you have to have them in your code at
11:18some point but don't put them in your
11:21main code your domain code because you
11:24won't be able to test
11:25them and what about codes uh what about
11:28functions that have an output but no
11:30input we said that was a bad design
11:32because where's where's the stuff coming
11:34from well reading anything from a file
11:36system reading something from the
11:38database generating a random number
11:40getting the current time these are all
11:42things where data is kind of made up out
11:44of thin air and given back to you so
11:47these are all unpredictable they're
11:49non-deterministic it's really hard to
11:51test them because there they got no
11:53input right so these things are all bad
11:56things to have in your
11:58code so so hopefully I've persuaded you
12:00that IO is a bad
12:02thing so the the the phrase functional
12:06core imperative
12:07shell was used to to way of saying this
12:11that you want pure code you want
12:13deterministic code in the middle and
12:15that's yes you have to have IO but
12:16you're going to keep the io around the
12:18edges and that that phrase by the way is
12:20created by Bar Gary burnhard and he has
12:22a whole talk about it as well um so
12:25that's the that's the vision right so
12:27now that we've have those guidelines
12:29about having all the explicit inputs and
12:31having an output and having everything
12:33be predictable and deterministic let's
12:36actually refactor some
12:38codes so again deterministic business
12:41logic non-deterministic IO so the
12:45business logic another way you think
12:46about business logic is you're making
12:48decisions like given this data and this
12:49data and this data what should I do
12:51should I save this thing should I send
12:53this message you know that's the logic
12:54you're doing right so let's look at the
12:56first one this is a bad design and we're
13:00going to fix
13:02it right so this is a function that
13:05compares two strings i' I've really
13:06trying to keep it as simple as possible
13:08okay so what it does is it reads the
13:10first string from the
13:12console reads the second string from the
13:14console it Compares them and if the
13:16first one's bigger it writes bigger and
13:19if the first one's smaller it writes
13:20smaller and otherwise it writes equal
13:22right so this is a very simple
13:24function
13:26now what's wrong with this design well
13:30it fails our guidelines completely
13:32because if you look at the input there
13:34is no
13:35input right it's it's got no input and
13:38if you look at the output there is no
13:39output it's a void returns void so how
13:43can you even test this function right
13:45this is a really badly designed function
13:47I mean it's easy to write but from our
13:49point of view it's really badly designed
13:51right it was exactly what I said that
13:53you shouldn't do what idiot would write
13:55a function with no input and no output
13:56well we just did you know it's it's very
13:58easy to to do right so it's not
14:01understandable if I give you this code
14:02it's not understandable how to use it
14:04because you don't even know where the
14:04strings are coming from they're they're
14:06magic and can you test it no you can't
14:08test it right
14:10because you know there's no inputs and
14:12no outputs so this is a really bad
14:15design so yeah we it's very easy to I
14:18mean I'm making fun of designs that have
14:19no input and no output but it's actually
14:21really easy to write stuff like this if
14:23you every time you have a thing that
14:24returns void if you have any methods
14:25that return void or any methods that
14:28have no inputs you've written code like
14:30this right so right now let's see if we
14:34can fix it well first of all let's just
14:36analyze this a little more here's all
14:38the code that does IO all the stuff in
14:41yellow is doing
14:44IO right and then all the code in green
14:47this is the code that's making decisions
14:50it's the logic part and you can see that
14:53the logic and the decisions are mixed up
14:57with the the io side they
14:59interleaved and it just makes the whole
15:01thing hard to understand I mean a lot of
15:03this code has nothing to do with the
15:04business it's all about how do I get the
15:05strings you know how do I read the
15:07strings I don't care how you read the
15:08strings whether you read them from the
15:09database in the file I don't care the
15:11logic is how do I compare two strings
15:13that's what I'm actually trying to get
15:15at so the first four lines are just a
15:17waste of
15:18space so we've got this
15:20interleaved code and let's fix it by
15:24separating the decisions from the io
15:27that's the way to fix this thing so
15:29first thing I'm going to do is I'm going
15:30to create a special data type to
15:32represent the
15:34decision in this case this is a very
15:36easy decision is actually I can just use
15:38an enum right so I've got an enum that
15:40represents the three decisions I've made
15:43bigger smaller or
15:44equal and then I write my I take
15:47basically the same code but I'm passing
15:51in the two strings I'm not getting them
15:54from the console I'm actually passing
15:55them in as
15:57parameters and then in ter of writing to
16:00the console when I've made my decision
16:02I'm not actually doing any IO I'm
16:05actually returning the
16:07decision right so you can see I've got
16:09explicit inputs I've got two
16:12inputs right and I've got an explicit
16:14decision being
16:17returned so this function has two inputs
16:20and one output this is a very this this
16:23totally matches the guidelines that we
16:24want right it's very explicit inputs
16:26there's no magic happening it's
16:27completely deterministic if I if I pass
16:29the same two strings in every time I'm
16:30always going to get the same answer
16:33right so it's very easy to understand
16:34it's very easy to test so this is what
16:37it looks like using this diagram there's
16:38two inputs and one output so this is
16:40really easy to test I mean I can
16:41literally write a test like this I can
16:42call this thing pass into two strings
16:45and I would expect to get back one of
16:47the enums right and I can literally just
16:49write
16:51code so we've turned a function which is
16:53impossible to test into something which
16:56is easy to
16:57test right and and and it's also much
16:59more explicit it's more it's more
17:01understandable because the enum actually
17:03represents what the decision is it's
17:04actually telling me kind of in English
17:06what I'm actually doing rather than kind
17:08of hidden in the
17:10io now of course we still need to get
17:13the
17:14data from the console so that's what I'm
17:17calling the Shell Code so we've now got
17:19two pieces one is the kind of pure
17:21business logic right and now we're going
17:23to write the shell which is the
17:24imperative part which actually does do
17:26the I/O so there's the same IO code we
17:29had before we're getting the two strings
17:31but now I am calling this business
17:34logic and I'm actually getting a result
17:37back and that's the enom I'm getting
17:38back with the three choices in it so now
17:41my IO after I've got the result back
17:43from the business logic I can just do a
17:45switch on that enum and it's like if
17:47it's bigger then I do the writing out
17:49and if it's smaller I write out
17:50something else and if it's equal I write
17:52something so the io is now separate from
17:55the business logic and if we do this
17:57thing of coloring them in you can see
17:59that all this is all
18:01IO this is all business logic and this
18:04is all IO so we actually have exactly
18:07the pattern we wanted where there's
18:09IO business logic iio we've got that
18:11kind of sandwich right so this is a good
18:14design so if you like little uh diagram
18:17I had there down at the bottom this
18:20matches that diagram
18:21perfectly so this is good right so I'm
18:24not saying get rid of IO right I'm mean
18:26saying you still have to do the io but
18:28the io is now separate from the business
18:31logic now one of the nice things about
18:34doing this is that it makes it much more
18:37flexible in terms of where do you get
18:38these things from so if you for example
18:42you know the first one we get it from
18:43the console but maybe you want to get it
18:44from a database right so I don't know
18:46why you want to get it from database but
18:48there you go so we have two different
18:50versions of this code and we have two
18:53different sources of data or two and
18:55syncs for data now you say well how can
18:58i e switch between these two different
19:01uh sources and if you're an object ored
19:04programmer you might say well let me
19:06abstract out the differences right I'm
19:09going to abstract the way the
19:10differences I'm going to create an I'm
19:12going to use inheritance I'm going to
19:13have some sort of interface some base
19:14class or an interface and I'm going to
19:16create a subass for the different data
19:19sources I'm going to have one for
19:21reading from the console want I have
19:22another one from Reading from the
19:23database and because I want to mock this
19:26maybe I'll have a mock one that you know
19:27or something I can play with for
19:30testing so I think this is actually too
19:33complicated you don't have to make it so
19:35complicated you don't have to create an
19:37interface and all this stuff if you do
19:39it this way if you do it my way with the
19:42the the business logic separate you
19:43don't need all this stuff if you do it
19:46with the the the business logic in the
19:47middle pure business logic you can
19:50literally write the workflow the console
19:52one you know reads from the thing and
19:55then does the business logic and writes
19:56the thing and then the database one
19:58reads from the database and you know
20:00does the business logic and writes the
20:01database so you just write two different
20:03implementations you don't need to have
20:05some generic abstract interface to share
20:08stuff so this is what people call
20:11composition over inheritance right
20:12you're just building things from smaller
20:14pieces you don't have to have interance
20:16and interfaces it just makes things
20:18complicated just like write it very
20:20straightforwardly and notice that we
20:21don't need any mock thing right the
20:23business logic is unchanged we don't
20:26need interfaces and if we want to test
20:27it we can literally just call business
20:28logic we don't need any special mock
20:31interface in order to call this stuff L
20:33you just call it I wrote the test
20:35already right you don't need anything
20:37fancy for doing the
20:38tests so this is what I think is a much
20:41much better way of doing this kind of
20:44stuff um here is here's the actual
20:46example of the shell so the the changes
20:49between the console version and the
20:50database version would just be in the
20:52Shell right the business logic does not
20:54change so here's the shell using the
20:57console with the with a right line and
20:59so on right and then here's the same
21:03shell but different version of the shell
21:05using database so whether I'm using a
21:08database or a console I just write two
21:09different implementations of the same
21:11thing but the Core Business logic is
21:15unchanged chances are you're not ever
21:17really going to need all these different
21:19things I mean again people leap to
21:21interfaces chances are you just rewrite
21:23the whole thing if you need to switch
21:24from a console you're probably not going
21:26to need multiple different data source
21:27you might just want to change the code
21:29to use a new data source but having two
21:31or three of them up and running at the
21:32same time is probably you know not so
21:34common really anyway so this is the nice
21:36advantage of this the the business
21:38hasn't changed at all I don't care where
21:40you get the data from I don't care where
21:42you put the data the business doesn't
21:44care because the business logic does not
21:46do iio at
21:47all okay now let's do that's a very kind
21:50of fake um example let's do a better
21:52example this is another example of an IO
21:54heavy code and in this case um
21:59let's say that you uh are a member of a
22:02website and you're updating your profile
22:04your customer profile on your website
22:07and it's your name and your email right
22:09so you fill in the form and you post it
22:12to the website and you've got a new name
22:14and a new email what is the website
22:16going to do with that the back end well
22:20um the first thing it's going to do is
22:21retrieve the existing data for you and
22:25the reason for that is like if nothing's
22:26changed right if it compared sh's the
22:28new and old data if nothing's changed
22:30there's no point updating the database
22:32maybe you double click the button or
22:33something there's no point updating the
22:35database twice if you if nothing's
22:38changed now if the name and email has
22:40changed then yes we do need to update
22:43the
22:44database and finally if you've changed
22:46your email we probably want to send you
22:48one of those verification messages
22:50saying you know do prove that you own
22:52this email by clicking on the link right
22:54so we've got three choices of things we
22:56need to do either do nothing just update
22:59the database or update the database and
23:02send an email right so this is a little
23:03bit more realistic obviously to fit into
23:05a slide it's going to be you know less
23:08complicated than the we one but you get
23:10the basic idea here so what I'm going to
23:12do is actually go to real code
23:15now so this is my implementation of
23:19this right so here I have my update
23:23customer and I'm passing in one
23:25parameter the customer and notice this
23:27is an a
23:28method because this whole thing is this
23:30customer database is everything is async
23:33so I get the existing customer and I'm
23:36checking is anything changed and if
23:38something's changed then I update the
23:40customer and if the email has changed as
23:42well I also create a new email message
23:45so here I'm constructing an email
23:47message with the with the subject line
23:50everything I want to send and then I
23:52just call the email server and send the
23:54message right so this is my impure
23:58version where the uh iio is mixed in
24:01with the business
24:03logic and again if you look at this it
24:06turns void I mean there is one
24:08input but you know it's not clear that
24:12there's any any other inputs needed for
24:14this thing so there's one input and it's
24:15also ret turning void so I can't really
24:17test this this is I mean how would I
24:19even test this piece of
24:20code right so let's refactor it to not
24:23do
24:25that so what I'm going to do here is I'm
24:27going to do the same thing I did before
24:30I'm going to start off by creating a
24:31decision a special data structure to
24:34hold a decision and here I've got three
24:37choices again I've got an enum right
24:38either do nothing or update the customer
24:40or do
24:41both and in this case there some extra
24:44data for each decision I might need some
24:47extra data so in the case of updating
24:49the customer I need to know what
24:51customer update and if I'm sending email
24:53I also need to know what the email to
24:56send is so I'm creating a little record
24:58here
24:59so it's got the enum
25:01here but it's also got the customer data
25:03to send and the email message to send
25:06now these are nullable because they're
25:07not applicable in every situation you
25:09could implement this using subclasses if
25:11you wanted to be a bit better but um I'm
25:13just being kind of lazy here but you
25:15know you get the idea i' I've just gone
25:16to I've just made a a very simple data
25:18structure to represent the decision that
25:20I'm
25:21taking so now if I look at the this new
25:26version both customers are part passed
25:29in right I'm the fact that the second
25:31customer has been
25:32loaded uh from the database that I'm not
25:35doing that here I'm doing it outside and
25:38passing in both
25:39customers right so this this does not
25:42have to hit the database and then
25:43secondly after I've done all the logic
25:46I'm not actually hitting the database
25:48all I'm doing is returning this result
25:51type so you know the F the default I do
25:54nothing and then if uh the things if the
25:57name address have Chang changed I change
25:59I Chang the decision to um update the
26:02customer only and then if the email has
26:03changed as well I have a new
26:06decision which is the uh update the
26:08customer and send the email
26:10both right so this is completely pure
26:13this is a
26:14deterministic pure piece of code right
26:16if I if I give you the same inputs I
26:19always get the same
26:21outputs right and and because there's an
26:23explicit input and explicit output it's
26:25really easy to test so this extra little
26:27effort yeah I Hadad of write um yeah I
26:30had to write some extra code to
26:31implement this decision thing but I
26:34actually think this is good because it
26:35actually documents this is kind of
26:37documentation of what's going on here
26:39right the previous one I could write
26:41documentation in the comments but this
26:42is actually code documentation that
26:45doesn't go out a date right if I got
26:46four choices I have to add another Unum
26:49so um this is this is much better I
26:51think this is actual real real
26:53documentation that lives with the
26:55code so how would I test this well
26:59really simple right I just
27:01create a customer
27:03here that's a version one of the
27:05customer and then version two of the
27:07customer I've changed the name of the
27:09customer but I've got the same email and
27:11version three of the
27:12customer I've got a new name and I've
27:14also got version two of the email so I I
27:18just literally call this code and I call
27:21it with the same customer and I would
27:22expect to get
27:23nothing and I call it with the second
27:26customer where it's just the name
27:27changed and I would expect to get this
27:29result and I call it with a third
27:32customer and I expect to get the send
27:33mail
27:35result so there you go so with a little
27:38bit of refactoring I've made this code
27:40much more easy to test and actually I
27:42think more self-documenting now the
27:45shell um is exactly what it was before
27:48I'm I am reading the custom from the
27:49database right this is the io code up
27:52here but now I have the pure code in the
27:55middle it's the sandwich right io on
27:57either sides pel code in the middle and
28:00then this is giving me a result again is
28:03and I just look at the enum and based on
28:05what the decision was I do different
28:07things so if the decision was do nothing
28:10I do nothing and if the decision was
28:12update then I actually write the
28:14customer to the database and if the
28:16decision was do both then I update the
28:18customer and send the
28:20email so I just think this is a much
28:22nicer way of doing it and just to prove
28:25that um you can actually see this for
28:28yourself I can actually I'm going to
28:29switch to F now I'm going to show you
28:31some interactive F which is kind of nice
28:34so one of the nice things that F does
28:35that makes us even easier is that you
28:38can mix uh data in with your enum so in
28:41the C you have your enum and if you want
28:44to have extra data associated with one
28:45of those choices you have to like put it
28:48in somewhere else I you could have a
28:49subass but it's a lot easier that way uh
28:52in fop these things called discriminated
28:54unions and um basically it's an enum
28:57it's an enum on steroids you can have
28:59choices and then for each choice you can
29:01have extra data for that choice so it's
29:03really really useful for domain modeling
29:05I highly recommend uh learning about
29:08discriminated unions and then you'll be
29:10upset that they're not in cop so this is
29:12my um this is my you know
29:16decision right and then here's my pure
29:18code right given the two Customs it
29:21looks very similar to the C- code right
29:24I'm basically
29:25returning uh no change or I'm update the
29:28customer only and so on so let me just I
29:30can literally just highlight this code
29:32and run it it's nice that it's
29:35interactive so there you go so it's kind
29:38of what I like about f is it feels like
29:40python you can play with it um but it's
29:43also compiled like sh you can do both
29:45very nice right now let's uh do some
29:50testing so there's
29:53my
29:55customers right so let's test this first
29:57one I'm passing the same customer twice
30:00and I would expect to get no change and
30:01Loan a hold I do get no change so I'm
30:03pretty sure my codee's working and if I
30:06pass in a name but no uh email then I
30:10get this new change update the customer
30:12only that's good now if I pass in the
30:13last
30:14one uh where the email has changed as
30:17well I get this email you see I got this
30:19little email thing you need to verify
30:21address and there is the enum that was
30:24returned update customer and send email
30:26so this is this is working really nicely
30:28so hopefully you can see how this works
30:31um I mean you know that's really in the
30:34ti point of this talk is create a
30:36special type to represent the decision
30:38separate your code into the pure code
30:41that Returns the decision and the impure
30:44code that's the
30:48shell um let's just uh review some of
30:51this first of all the pure code as I
30:53said it has explicit inputs and outputs
30:55where there's no magic going on I could
30:58I could see customers going in I can see
31:00a decision coming out that's exactly
31:02what you
31:03want um the P code was easier to test
31:06because again I can just pass in the
31:07inputs and I always get the same output
31:09and it's really easy to write a little
31:10test that checks all the different
31:11inputs and test the right out that's
31:13what it is notice that the pure code
31:16wasn't async because there was no IO I
31:20didn't have to worry about doing async
31:22because I mean in pure code you wouldn't
31:24have any async going because you're not
31:25talking to the outside world so so you
31:28don't have to worry about all this Ayn
31:29of weight stuff because it's not going
31:31to be
31:32there U another thing is that the pure
31:34code didn't have any exception Handles
31:35in it now when you're dealing with the
31:37outside world you need to do exception
31:40handling because you know what happens
31:42if the database isn't there so let me
31:43actually show you what that looks like I
31:46actually do have the exception version
31:49here right so here's the my bad
31:52version which returns void and inside
31:56here I have to put a big I put a whole
31:58all the business logic is in this try
32:00catch block so I have to try doing
32:03various things and then at the bottom I
32:06have to catch maybe the database record
32:08doesn't exist maybe there's some other
32:09database exception maybe I couldn't talk
32:11to the email server I have to catch all
32:13these things otherwise it's not really
32:14production ready code but this is my
32:17business logic containing all this
32:18exception handling now if I go to the
32:21pure
32:23version notice that there's no uh
32:27there's no exception handling in this
32:30code right there's because there isn't
32:32there's nothing to there's nothing
32:34database stuff now the exception
32:36handling you do need the exception
32:37handling but it's going to be in the
32:38Shell so you might have a a version of
32:40the Shell Code which has the exception
32:42handling in it absolutely so here's my
32:44shell handling code with exceptions
32:47right but it has the same pattern I mean
32:49everything's wrapped in a tri catch but
32:51even within this there's the io stuff
32:54then there's the pure stuff and then
32:56there's the more more IO stuff and the
32:57very bottom there's all the exception
32:59handling but it doesn't contaminate my
33:02business
33:05logic right now let's look at some
33:08common questions that people have what
33:10about unit testing what about
33:13integration testing and well I mean you
33:15saw how to do unit testing you literally
33:17just pass in the data and get the result
33:21you a test that you get the right result
33:23integration testing uh is like well how
33:25do you do integration testing if you
33:27don't have any IO and the answer is
33:29that's not where you do the integration
33:30testing this is how you do the
33:32integration testing you do your business
33:34logic is your unit test and your
33:37integration test is the entire pipeline
33:39including all the io so if if people
33:42always worry well where should I put
33:43when what counts as an integration test
33:45what counts as a unit test and you know
33:47where the boundaries between which and
33:49which is like if you do it this way the
33:51boundaries are really really obvious
33:53right the middle bit is the unit test
33:55and the whole thing is the integration
33:56test right so that put it'll put pay to
34:00any arguments there now of course
34:02sometimes you might want a fake iio you
34:04want to might mock the io um you never
34:08need to mock pure codes right you only
34:10need to mock something where it's
34:11non-deterministic where you can't
34:13actually do it like a database you
34:15really shouldn't be mocking real classes
34:17that are pure because there's no point I
34:19mean just use them right so if you do
34:21need to mock something again you you can
34:24mock the Io if for some reason you want
34:26to but um you should never be doing
34:28mocking in the middle
34:30bit it's not it shouldn't be needed
34:33right now let's just review some
34:36important testing Concepts because I
34:37think people sometimes get sidetracked
34:38by classes and unit tests and all stuff
34:41I'm just going to say some things to
34:43emphasize some things about testing
34:46testing should be about testing business
34:49value right it should be testing stories
34:51testing use cases scenarios whatever you
34:54want to call them it should not be about
34:56testing classes
34:58there's no point in testing a class
35:00you've probably all seen the little joke
35:02things where there's two unit tests and
35:04the failed integration test where the
35:06door latch doesn't fit I mean there's
35:07various you know gifs going around but
35:10yeah the unit test should be the whole
35:12thing you shouldn't test individual
35:13classes because that doesn't make any
35:14sense and then also you should test the
35:16boundaries there's no point testing the
35:18internals and also people say well how
35:19do I test something when the methods are
35:21private or something right well the
35:23answer is don't test it you test the
35:24entire thing why are you testing
35:26something where the methods are private
35:28and you can't even call them anyway
35:29right so don't test internals test from
35:32end to end in the whole system right do
35:35the test at the workflow level and all
35:37the unit test means is it's an isolated
35:40unit it doesn't mean a class it means
35:42something that can be isolated and run
35:43separately from all the other tests
35:45right so if you don't have any side
35:46effects you're not write in the
35:48database these business logic things are
35:50completely the definition of units that
35:53you can test they're completely isolated
35:55you could run them all in parallel
35:56they're all really fast cuz they're not
35:58doing any IO that's exactly what the
36:00kind of thing you want to be testing
36:01right so a unit is not a Class A unit is
36:03a unit of functionality just want to
36:05reemphasize that because people forget
36:08sometimes right so here we go here's our
36:10workflows kind of slicing through our
36:13our kind of donut architecture here and
36:16these are the things you want to be
36:17testing right you want you don't want to
36:18be testing individual classes test the
36:20workflows because sometimes they you
36:22know things interact the pieces can work
36:24but the whole thing might not work
36:26properly okay another question people
36:28have is where does validation fit
36:30in well here's where it fits in it
36:34doesn't it doesn't belong in your
36:36business logic validation happens after
36:39you've loaded the stuff or you you know
36:41you've read the Json from the network
36:42and you've loaded the file from the
36:43database whatever you need to validate
36:45that the data is
36:47good but you do that before you send it
36:50to the business logic right by the time
36:52you get to the business logic it should
36:54be 100% validated you should never worry
36:56you should never be doing defensive
36:57stuff like well maybe this isn't right
36:59that should all be done on the outside
37:01not on the inside right so you do
37:03validation of the
37:04edges not not in the
37:06middle right and if you do it right you
37:09really shouldn't be doing null checking
37:11in the middle of your domain I mean you
37:12should a lot of people have like very
37:14defensive programming like what happens
37:15if this is bad it's like if you do your
37:17validation
37:19properly um your business do should be
37:22free from defensive programming which is
37:23nice and if fation fails just skip right
37:27you load something up and it's actually
37:29not valid the email doesn't have at sign
37:31whatever there's something wrong the
37:33string is too long to put in the
37:34database whatever some reason it doesn't
37:36it doesn't work now you can just skip
37:37the entire business logic and spit out
37:40some sort of error at the back end right
37:42so again again it fits really nicely if
37:44you have validation mixed up with your
37:46business logic it's really hard to know
37:48you know what to
37:50do okay ORS like Entity framework and
37:54hibernate and so on unit of work people
37:56ask about this
37:58I personally don't think that ORS
38:00especially heavy arms um don't really
38:03fit with this because I mean I'll give
38:05you some code this is kind of typical uh
38:08or kind of code you create something and
38:10you save it to the database uh you do
38:13something else and you send it you
38:14notice this save look at that save
38:16method there's no input no
38:18output that's bad I just I just got
38:21through saying anything where there's no
38:22input and no output is is badly designed
38:25and and the save method is badly
38:26designed
38:28right so don't do that it's right in the
38:31middle of your domain logic here so I
38:35mean that's very convenient I mean
38:37certainly is convenient to do it this
38:38way but it's very hard to test and it's
38:41very brittle if things change now some
38:44Frameworks you know um Jango Ruby on
38:47Rails that stuff they they do this all
38:48the time and for basic website you know
38:51fine not a problem but if you're trying
38:53to do like a a domain where things the
38:55rules business rules might change and
38:57you want to have have a nicely
38:58documented code I personally don't think
39:00this is that
39:02good so yeah don't put I/O in the middle
39:04of your logic and it's really easy to do
39:06with an or that's the problem because it
39:07generates all these methods for you half
39:09the time so don't do that now there's
39:11something else called unit of work which
39:13is a slightly more sophisticated version
39:15and the idea here is you don't actually
39:17write to the database you collect all
39:20the things that you're going to be doing
39:21and at the very end you write everything
39:23into the database right so we're not
39:25actually saving it to the database
39:27adding it to the context we're adding
39:29something more to the context at the
39:30very end we save all the changes I still
39:33think this is bad first of all it's very
39:35database Centric because I can't really
39:38send an email here I mean I can do
39:40database stuff but if I'm trying to send
39:42an email in the middle of this I can't
39:44add the email to the database as well
39:45you so it only works for like database
39:47stuff and it's really still the same
39:49thing it's still kind of hard to test I
39:51mean if you want to see what's actually
39:53going on I personally think having
39:55explicit um
39:58result type that you divine that
40:00represents the decision is a much nicer
40:02way of doing
40:03everything so yeah personally I wouldn't
40:06do this so I think that if you do if you
40:09follow this design where you have a pure
40:11um core and you have the imperative
40:13shell you actually don't need a heavy
40:15arm at all in fact I I haven't used
40:17ntity framework for years if I need to
40:20write something into the Ws I would use
40:21something like Dapper something very
40:23simple something very explicit where you
40:25can actually write the SQL code yourself
40:26and or you you know whatever it is that
40:27you're doing um it just it's a lot
40:30easier and it's much more transparent so
40:33that's what I recommend now people
40:35always say what if I really really
40:37really need to do IO in the middle of my
40:39workflow please please I need to do this
40:41well yes of course sometimes you need to
40:42load something up you make a decision
40:45you need to load a pick record from the
40:46database and then based on that you make
40:48another decision and then you know save
40:50something and make that's a legitimate
40:52workflow sometimes I think it's less
40:54common than people think but yeah it's
40:56legitimate you answer is you just break
40:58it into pieces the same
40:59way right you you break it into little
41:02pieces and each piece is either Pure or
41:04it's IO but not both right so each of
41:07the little pieces are testable right and
41:09each little piece can be unit tested now
41:11the other thing about unit the iio
41:13doesn't normally need to unit test I
41:15mean writing a record to the database
41:17you don't really need to unit test that
41:18hopefully your code works right and I
41:22mean if you've made a mistake in the SQL
41:24code or something you'll C hopefully
41:26you'll catch the integ testing we don't
41:28really need to write a unit test to
41:29check that the record was successfully
41:31successfully stored in the database
41:32that's like a waste of work really so
41:34save your time for testing the business
41:38logic right so here's if you're doing a
41:40code review and you want to know and
41:43you're looking at your domain code and
41:45you're saying is my domain code
41:47following these good design principles
41:50here we go do you have any async
41:52anywhere if you do you're doing IO
41:55somewhere that's bad are you catching
41:57exceptions in your domain code if you
41:59are probably you're doing I maybe maybe
42:03not but chances are you are are you
42:05throwing exceptions why will you be
42:08throwing exceptions in your domain logic
42:11like well you know I need to do a choice
42:13of this or this like just return a
42:16proper return value here I got one of
42:18these enums if I want to say this or
42:20this or this rather than throwing some
42:21sort of exception I'll just literally
42:23have a proper return value which
42:24represents the three choices the three
42:25decisions right don't use exceptions for
42:28control
42:29flow so if you see these things in your
42:31pure domain code that's it clue that
42:34it's time to refactor your code so keep
42:37your iio at a distance okay it's can be
42:39really dangerous put up one of those
42:41electrified fences around your core
42:44domain and keep your eye
42:47outside right now a lot of these talks
42:52people always say my solution is the
42:54perfect solution and everyone should go
42:55ahead and do my solution because it's
42:56perfect perf and I'm never I'm not that
42:59kind of person I would never ever say
43:00that there is never a silver bullet and
43:03there's no approach that fits every
43:05solution so I'm just recommending this
43:06as an approach that you try out but I'm
43:09not saying that it's a perfect approach
43:11you know so anyone who says my way is
43:13the best uh I always I never trust that
43:16because it's the real answer as you know
43:19as as the classic senior developer
43:21answer is it depends right if someone
43:23doesn't say it depends then they're not
43:25senior enough
43:28so okay so let's just I mean I pretty
43:31much finished with the keeping the eye
43:32of the edges but let's just look at some
43:34other ways of doing dependencies if for
43:36some reason you really hate this
43:37approach by the way this approach that
43:39I've talked about is the best approach
43:40and you should all do it all the time
43:43but if you don't think it's the best
43:44approach let's look at some other
43:46ways so um now I'm talking about IO
43:49dependencies I mean dependency can mean
43:51all sorts of things why you have
43:53dependencies on binaries and
43:54dependencies and this when I mean
43:56dependency I mean something that
43:58introduces non-determinism into your
44:00code something that gives different
44:02answers every time you run it so that
44:04includes iio that includes random
44:05numbers that includes getting the
44:06current time um it could also include
44:09things like the strategy pattern if you
44:10really are doing interfaces and
44:11injecting things in then you really
44:13don't know what's happening um I
44:16honestly I hardly ever use the strategy
44:18pattern because I use composition just
44:19like I showed you where you build things
44:21some smaller things but anyway right um
44:24anything else which is not which is
44:26deterministic pure code cod in your
44:27domain is not a dependency it doesn't
44:29need to be managed you don't need to
44:31mock it just call it all right just if
44:34it's there and it's pure and it's not
44:36having any side effects just use the
44:38real class don't have an interface for
44:40it and don't mock it so what of the
44:42strategies you can use for managing
44:44dependencies well here's five first one
44:48is dependency rejection not dependency
44:52injection but dependency
44:53rejection just like keeping the T-Rex
44:56away from the rest of the people here so
44:58the the dependency question is you don't
44:59have any
45:00dependencies right you keep them away
45:03from your pure code now the second thing
45:06is I'm going to call it dependency
45:08retention you keep your dependencies in
45:11fact you don't worry about them at all
45:13you just let them all hang out and do
45:14their thing that's fine and then there's
45:17dependency injection which you all
45:19familiar with hopefully and that's where
45:21you pass the dependencies explicitly
45:23into the class probably through a class
45:25Constructor and then there's what I'm
45:27calling parameterization which is
45:29slightly different because you're in
45:30this one you're actually passing as
45:32parameters into the code rather than
45:33through the class Constructor you're
45:35passing directly into the methods and
45:38then finally there's dependency
45:40interpretation where you actually build
45:43a custom language for your domain and
45:46then you interpret it separately now
45:48that's a lot of work I'll I'll quickly
45:50go over that but that's not something
45:51you probably want to do okay let's look
45:53at dependency rejection this phrase by
45:54the way is coined by Mark Seaman who
45:58literally wrote the book on dependency
46:00injection and so he knows what he's
46:02talking about um and so he wrote he came
46:05up with this phrase and I've come up
46:06with other ones kind of copying him so
46:08dependency rejection owes that to Mark
46:10and that's what exactly what we just
46:11talked about earlier it's like no
46:13dependencies on your core business logic
46:15so that's definitely my recommended
46:17approach it's easy to it makes your code
46:19much more comprehensible there's a
46:20there's a report I just saw on someone
46:22on Twitter say um that we spend 80% of
46:25our time trying to understand code we
46:28don't spend you know 10% 15% is actually
46:31writing code most of our time is spent
46:33trying to understand code anything we
46:35can do to make our code more
46:36understandable is a good thing so by
46:38having an explicit result like I did
46:40with the three choices that makes the
46:41code a lot easier to understand what's
46:43going on so it's easy to comprehend easy
46:45to test it is a bit of extra work to
46:48document these decisions with a special
46:50type but it's not that much work I mean
46:52it's like you know a few lines of code
46:54it wasn't that big deal and I think it
46:55makes the code easy to understand so
46:57definitely this is a good thing so
46:59dependency retention is where you just
47:00don't care about your dependencies you
47:02just hardcode all the file access you
47:04hardcode all the database access you
47:06don't care now like I say Ruby and Wales
47:09and Jango sort of does this uh which
47:11doesn't make testing slightly
47:13interesting because they typically do
47:14like inmemory SQL databases or something
47:18but I mean it's just appropriate for
47:19when you're doing like ETL data
47:21transformation data science where you
47:23just like reading files and writing
47:25files and all sorts of weird stuff
47:27there's no point creating special
47:28interfaces and special decisions for all
47:30this stuff just like you've got a python
47:31script you know you're just running your
47:33python script fine you don't need to go
47:35to all this work so that's very
47:36appropriate you're doing kind of any
47:38exploratory coding when you're testing
47:40things you don't need to create special
47:42decisions and interfaces all too much
47:44work any kind of throw away scripts like
47:47you know if I'm just like loading some
47:48stuff into the database no I little
47:49script to do that I don't need to like
47:51separate the database code out right
47:53it's not worth it so you know this is
47:55perfectly valid for many situations I'm
47:58not so you know I'm quite happy to do
48:00this so you don't always have to have
48:02the fancy way of doing things now
48:05injection and
48:08parameterization so in injection we're
48:11passing all the dependencies to the
48:12class as a whole and in
48:15parameterization we're passing only the
48:18dependencies needed for a particular
48:21function right so that's the difference
48:23one's a parameter and one's class
48:25Constructor basically
48:27so let me actually explain what I mean
48:29by this let's go to the injection one
48:31first of
48:32all right so here is the same code
48:36again and now I'm what I've got here is
48:39I've got an interface that represents
48:41all my database stuff so I got an inter
48:42I got a a method to read the customer
48:44and I got a method to update the
48:46customer oh and I have a method to
48:48update the delete customer and update
48:49the password and also maybe get some
48:51customers so all these things are in
48:53this
48:54interface okay and then I have another
48:56interface to represent the email
48:59service and um I you know this classic
49:02thing I have my local Fields I inject it
49:04into the Constructor now here's my code
49:08so here I am in in instead in this case
49:10I'm getting it from the the injected
49:13interface I'm doing the code that way
49:16other than that the code is exactly the
49:17same now from my point of view this code
49:19is still equally bad because again it
49:21returns nothing it's hard to test you
49:23know you could test that it's reading
49:24and writing mean you could test you can
49:25mock up the interface and test that it's
49:27reading and writing to the
49:28database but that's just testing and
49:30Reading Writing logic if I have the if I
49:33return the actual decision I wouldn't
49:35have to do any of that test I could
49:36literally just check the decision so
49:38mocking up an interface and then trying
49:40to see did it actually write to the mock
49:42at the right time and the right things
49:43like just return the decision then it's
49:45a lot easier so anyway but this is how
49:47you would do it this is the classic um
49:51uh you know a Constructor injection and
49:54you might say well you know why do I
49:56have the password in here well the
49:59answer is because I have another method
50:00down here which updates the password and
50:04again there's a pure part to calculate
50:05the hash and and then I also have
50:07another method here which gets the
50:09customers by city so I need all these
50:11things so I have to pass all these
50:13things in I have to create the interface
50:15which has all these things now it also
50:16has delete customer and nobody's using
50:18delete
50:19customer um so probably at some point I
50:21had a method which did it and now I'm
50:23not having that method anymore but it's
50:25still in the interface because once
50:26things get into an interface it's really
50:29hard it's kind of scary to take them out
50:30because you might break
50:31something so it's very easy to have
50:34interface creep right interfaces get
50:35bigger and bigger you keep adding things
50:37to them and you're kind of because
50:39you're already injecting them rather
50:41than going to create a new interface you
50:42might just add it to the existing
50:43interface and the interfaces kind of get
50:45bigger and bigger and more and more
50:47gnarly um so this is an example and we
50:50also have things where I'm not you know
50:52why am I passing in the password thing
50:54where this method doesn't need the
50:56password so if we compare this with a
50:58parameterized
50:59version in the parameterized
51:01implementation I do not have an
51:03interface right I Define
51:05three these are the three things that
51:07this function needs it needs to read a
51:09customer it needs to update a customer
51:11and it needs to send email message those
51:12are the three things it needs and I'm
51:13going to pass them in as parameters to
51:15the
51:18method so rather than just passing in
51:20the customer here I've actually got
51:23three other parameters so this is
51:25everything this method needs nothing I
51:28don't need to pass in delete a customer
51:30because it doesn't need it I don't need
51:31to pass in how to update a password
51:33because it doesn't need it right the
51:35only things that I need to pass in are
51:37the things which explicitly that I'm
51:39using now if I pass in something which
51:40I'm not using anymore I'll get an unused
51:42variable error and so that will tell me
51:45if if I'm not using anything more so
51:46it's very clean and you there's a lot of
51:48pressure to keep these small right
51:49you're not going to have 10,000
51:51parameters here it's it puts pressure on
51:54you to have small methods that do one
51:55thing if you start having you know
51:57hundreds of parameters obviously your
51:59methods are too big and they're doing
52:00too many
52:02things and then again here's the
52:04updating the
52:05password and all I need to do is this
52:07one method that updates passwords I
52:09don't I mean this one database access
52:11that updates password I don't need to
52:13get a customer read a customer you know
52:14query the database so I just pass in
52:17this one thing so this is much more
52:18self-documenting
52:20I can see at a glance these are the
52:22inputs that I need to do my job and I'm
52:25never going to have a delete customer
52:26Customer because none of these things
52:27need a leite
52:28customer so I think the the parameter by
52:31passing a parameters is much better than
52:34passing an interfaces if you can get
52:35away with it even better is not is not
52:37doing any of this stuff but if you have
52:39to do it I think parameters are better
52:40because here's an example of interface
52:42creep again right so I start with insert
52:45delete commit changes query maybe
52:47another query maybe another query maybe
52:48another query and then maybe some of the
52:51contains and then I optimize that and
52:53then maybe I need a new kind of summary
52:54return and maybe a version to this and
52:57maybe here's my change password oh a new
52:59version with an extra hash or something
53:02oh and I by the way for testing I want
53:04to delete all the rows in the database
53:06just you know for testing
53:07only but it's in this interface are you
53:10going to guarantee that nobody else none
53:12these other methods accidentally ever
53:13call this method by mistake it's kind of
53:15dangerous how about launching missiles
53:17what is that doing here what about
53:19launch missiles version two so why is
53:22launch M because I was too lazy to
53:24create a new interface I like I've
53:26already got this interface I'm already
53:28injecting it in let me just add another
53:29method to the interface even though it's
53:30got nothing to do with anything else
53:33right so it's very easy to do I mean
53:35that's the problem is we've all done it
53:36I've done it I'm not saying I'm just
53:38saying it's very easy to have this kind
53:39of interface creep and and because this
53:42is so bad there's even a thing inter
53:44face segregation principle which is like
53:45don't do this but the natural tendency
53:49the kind of inertia if you're lazy
53:51you're going to do it the lazy thing is
53:53to add more stuff onto your
53:55interface right and that goes against
53:57human nature it we kind have to force
53:59our we're going against our own nature
54:01to not keep adding
54:03things so the advantage of doing
54:05injection is yeah we everyone
54:07understands how it works Frameworks
54:09already have ioc libraries and all this
54:11stuff very nice but we do have this
54:13interface creep thing and if you do if
54:15you're
54:16lazy it's easy to do the wrong thing and
54:19I think that's a bad design a good
54:22design that should force you to do the
54:24right thing the lazier you are the more
54:26the right thing you should do right a
54:28design where if you're lazy you do the
54:30bad thing is a bad design and like you
54:33see you get these interfaces with you
54:35know hundreds of methods on them and
54:36most of them are not in need at half the
54:38time and someone might accidentally call
54:39launch missiles or delete all the those
54:41just because it's in the interface and
54:42they have a typo you know all this stuff
54:44so they're not so good now
54:46parameterization I think is better
54:47because you do have these explicit
54:49dependencies and you're passing in
54:51individual focused functions that do one
54:53thing rather than whole interfaces and
54:56it avoids uh interface creep because the
54:59exact opposite the lazier you are the
55:01less stuff you're going to pass in right
55:04so you have a natural tendency to create
55:05smaller things with less dependencies so
55:07being lazy is going to encourage you to
55:09do the right thing now unfortunately
55:11it's kind of hard to work with when you
55:12have deeply nested code because the
55:15Frameworks don't really support
55:16dependency injection for for these kinds
55:18of functions I mean there are ways of
55:20doing it but you know the Constructor
55:22injection is the standard way of doing
55:23it so it's kind of hard to work with and
55:25it still doesn't really really help
55:26because in terms of you know we still
55:28have these void right I mean I'm passing
55:30in all the database stuff but even
55:32better would be not passing any datab
55:34best stuff at all and just literally
55:35pass in the things I need and return the
55:37decision so I mean this is kind of easy
55:40you know people feel comfortable with it
55:42but I still don't think it's the right
55:43thing to
55:44do and the last thing is what I'm
55:47calling dependency
55:49interpretation so what you do here is
55:51every call to a dependency is actually
55:54you write it as an instruction
55:57uh This Is The Interpreter pattern if
55:58you go to the the gang of four pattern
56:00book it's called The Interpreter pattern
56:02and if you're a functional programmer
56:03you call it freeone ads um but what you
56:06do is you create uh instructions that
56:09you call which you interpret later on so
56:12you're actually kind of writing kind of
56:13B code for your program you're not
56:15actually doing any IO in your code and
56:17then you have a separate
56:19interpreter that interprets the bik code
56:23that you've written obviously it's not B
56:24code it's just regular data structure
56:26but it's you basically create this data
56:27structure in memory that you interpret
56:30saying every time I see this instruction
56:31I need to write the database every time
56:33I see this instruction I need to read
56:34from the database you know so it's a lot
56:37of work to do but is super super
56:38powerful because well let me just show
56:40you how it works right so you got an
56:41instruction that goes into The
56:43Interpreter The Interpreter gives you an
56:45output and then you based on the output
56:47you build another instruction and so on
56:49so forth so it's it is a classic
56:51interpreted thing but it's done you know
56:53in in writing kind of interpreted in in
56:56uh code rather than you know somewhere
56:59else I don't I could easily write a
57:00whole talk on this and maybe I will one
57:02day but it's a lot of work however it
57:04does have some pros over all these other
57:07ones the first thing is you get a
57:09completely domain oriented API which is
57:11specific to your domain if you're doing
57:13something very specific uh I mean a good
57:16example is Twitter where to construct a
57:19tweet they have to go over here and get
57:21your profile then have to go here and
57:22get the the hashtags and go over here
57:24and get your followers and go over and
57:26you so they have all these microservices
57:27and they were going out and getting all
57:29this stuff it's the same thing as they
57:31have you know 20 microservices they have
57:33to go and get to to get a tweet going so
57:36they have a specific API that they
57:37defined and they could actually write a
57:39special um instructions for doing that
57:44no IO at all right the io happens later
57:47when you actually interpret it now
57:49what's nice about this is is that your
57:51instructions this is doain this is
57:52basically a domain specific
57:54language so the actual database calls
57:58whatever you don't actually care how
57:59they're done right there completely
58:01separate so if they change their API for
58:03how you talk to the database or here you
58:05talk to the file system or how you talk
58:06to CF or whatever you don't care not
58:08your problem now this is where it's
58:11really this is where people like Twitter
58:12and Facebook are into it because uh
58:14let's say that your database charged you
58:17every time you didn't
58:19update maybe or may performance-wise you
58:22do millions of updates a second or
58:23whatever you just can't handle it what
58:25you can do because it's a data structure
58:27you can merge the data structures so you
58:30can merge all the database updates into
58:31one batch update and you can merge all
58:34the reads into a single read so if
58:35you're doing microservices you know you
58:37take 100 operations you merge all the
58:41oper all the instructions into one batch
58:44you set them all over as a batch and
58:45then you bring them back again so you
58:46you've replaced 100 API calls with one
58:49single API call so it's more efficient
58:51and like I say if you if if you got an
58:53API that charges you money that'll save
58:54you money so optimization is one of the
58:57big reasons for doing this and of course
58:59you can do different interpretations of
59:01the same thing you can have a test
59:02interpretation and you have a real
59:04interpretation that hits databases and
59:05so on so really really complex and it's
59:09not not a very general purpose tool but
59:12um if you're in interested there Twitter
59:14has a whole talk on their Stitch Library
59:16they called it Stitch Facebook kind of
59:18Library called
59:19haxel and they've I think they've done
59:21talks and blog posts about it so go and
59:23check them out if you're interested in
59:24this approach and I have talked about it
59:25a little bit some of my other talks but
59:27not today right so let's finish up avoid
59:31pure code avoid non-pure code in your
59:34core domain that's the main takeaway
59:37from this talk okay if you do that uh if
59:40you do have impure code it's going to be
59:42really hard to test and really
59:43unpredictable so instead the first thing
59:45you should do is push the I/O to the
59:48edges using dependency rejection which
59:49is what I talked about most if you
59:52really can't do that or your colleagues
59:55can't stand doing that or whatever then
59:57try using you know dependency injection
59:59or parameterization and if you really
01:00:01are Twitter or Facebook and you need
01:00:02some really complex thing do look at The
01:00:04Interpreter pattern uh it's kind of
01:00:05interesting but it's probably overkill
01:00:07for most people so that's the end of my
01:00:10talk I'm going to put the slides and
01:00:12videos again on my website if you like
01:00:15this one I have lots of other talks on
01:00:16my website um I have one on composition
01:00:18which is functional programming and I
01:00:20have one on Pipeline we programming
01:00:21which is kind of fun and then of course
01:00:23I have my book which people seem to like
01:00:26so um yeah thanks very much
01:00:28[Applause]
🎥 Related Videos![What vaccinating vampire bats can teach us about pandemics | Daniel Streicker](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FlSPxeA6Z_m4%2Fhqdefault.jpg&w=3840&q=75)
![a16z Podcast | Things Come Together -- Truths about Tech in Africa](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2Fjbl5I7kYWHM%2Fhqdefault.jpg&w=3840&q=75)
![2024 TSCRS Applications of anterior segments diagnostic instruments in cataract surgery](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FWnUDk5vKuqE%2Fhqdefault.jpg&w=3840&q=75)
![a16z Podcast | The Infrastructure of Total Health](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FoLGRslHa5FE%2Fhqdefault.jpg&w=3840&q=75)
![The Robot Lawyer Resistance with Joshua Browder of DoNotPay](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FAmVdYPTdw2c%2Fhqdefault.jpg&w=3840&q=75)
![NES Controllers Explained](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FnAStgQzPrAQ%2Fhqdefault.jpg&w=3840&q=75)
![What vaccinating vampire bats can teach us about pandemics | Daniel Streicker](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FlSPxeA6Z_m4%2Fhqdefault.jpg&w=3840&q=75)
What vaccinating vampire bats can teach us about pandemics | Daniel Streicker
![a16z Podcast | Things Come Together -- Truths about Tech in Africa](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2Fjbl5I7kYWHM%2Fhqdefault.jpg&w=3840&q=75)
a16z Podcast | Things Come Together -- Truths about Tech in Africa
![2024 TSCRS Applications of anterior segments diagnostic instruments in cataract surgery](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FWnUDk5vKuqE%2Fhqdefault.jpg&w=3840&q=75)
2024 TSCRS Applications of anterior segments diagnostic instruments in cataract surgery
![a16z Podcast | The Infrastructure of Total Health](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FoLGRslHa5FE%2Fhqdefault.jpg&w=3840&q=75)
a16z Podcast | The Infrastructure of Total Health
![The Robot Lawyer Resistance with Joshua Browder of DoNotPay](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FAmVdYPTdw2c%2Fhqdefault.jpg&w=3840&q=75)
The Robot Lawyer Resistance with Joshua Browder of DoNotPay
![NES Controllers Explained](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FnAStgQzPrAQ%2Fhqdefault.jpg&w=3840&q=75)
NES Controllers Explained
🔥 Recently Summarized Examples![4 Steps to Master Any Complex Skill (quickly)](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FpHN7BXpdAPQ%2Fhqdefault.jpg&w=3840&q=75)
![40 Years of Fitness Experience in Less Than 11 Minutes.](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2F2ZsHsX4hvlk%2Fhqdefault.jpg&w=3840&q=75)
![Gun Controlling Media Makes FATAL Mistake... They Have Tied Their Fate To Biden's & Gun Rights Win](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2F8L34kAwjriw%2Fhqdefault.jpg&w=3840&q=75)
![GET READY! Palantir Is Officially The Next Nvidia.](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FscpVS5--ikw%2Fhqdefault.jpg&w=3840&q=75)
![Abundant Thinking: The Hidden Key to Get Everything You Want (Audiobook)](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2Far9dcxJ11H4%2Fhqdefault.jpg&w=3840&q=75)
![The Coming Demonic Invasion (Revelation 9:12–21)](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FDYzkCtbwHqU%2Fhqdefault.jpg&w=3840&q=75)
![4 Steps to Master Any Complex Skill (quickly)](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FpHN7BXpdAPQ%2Fhqdefault.jpg&w=3840&q=75)
4 Steps to Master Any Complex Skill (quickly)
![40 Years of Fitness Experience in Less Than 11 Minutes.](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2F2ZsHsX4hvlk%2Fhqdefault.jpg&w=3840&q=75)
40 Years of Fitness Experience in Less Than 11 Minutes.
![Gun Controlling Media Makes FATAL Mistake... They Have Tied Their Fate To Biden's & Gun Rights Win](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2F8L34kAwjriw%2Fhqdefault.jpg&w=3840&q=75)
Gun Controlling Media Makes FATAL Mistake... They Have Tied Their Fate To Biden's & Gun Rights Win
![GET READY! Palantir Is Officially The Next Nvidia.](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FscpVS5--ikw%2Fhqdefault.jpg&w=3840&q=75)
GET READY! Palantir Is Officially The Next Nvidia.
![Abundant Thinking: The Hidden Key to Get Everything You Want (Audiobook)](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2Far9dcxJ11H4%2Fhqdefault.jpg&w=3840&q=75)
Abundant Thinking: The Hidden Key to Get Everything You Want (Audiobook)
![The Coming Demonic Invasion (Revelation 9:12–21)](/_next/image?url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FDYzkCtbwHqU%2Fhqdefault.jpg&w=3840&q=75)