Go Summarize

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|1 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.
Keeping 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.
Advocates 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.
Good 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.
Functions 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.
The 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.
By 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.
Separating 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.
The 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#.
Deterministic 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.
Pure 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.
Testing 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.
Validation 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.
Limitations 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.
Using 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.
Dependency 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.
Demonstrates 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.
Passing 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.
Laziness 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.
Merging 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.
Speaker finds it enjoyable that people like the book.