Notes and highlights for
Clean Architecture: A Craftsman's Guide to Software Structure and Design (Robert C. Martin Series)
Martin, Robert C.


PART I Introduction

Highlight (yellow) - Page 2 · Location 346

Has the design of the systems you’ve worked on had a huge negative effect on the morale of the team , the trust of the customers , and the patience of the managers ?

Note - Page 2 · Location 347

Can this be changed in an OLD project? how so?

Highlight (yellow) - Chapter 1 What Is Design and Architecture? > Page 5 · Location 371

The goal of software architecture is to minimize the human resources required to build and maintain the required system .

Note - Chapter 1 What Is Design and Architecture? > Page 5 · Location 372

THIS!

Highlight (yellow) - Chapter 1 What Is Design and Architecture? > Page 5 · Location 373

the design is good . If that effort grows with each new release , the design is bad . It’s as simple as that .

Note - Chapter 1 What Is Design and Architecture? > Page 5 · Location 375

For management : this! the goal of quality is to how little effort is to ship something. If my feature takes 100000 days vs a week to add

Highlight (yellow) - Chapter 1 What Is Design and Architecture? > Page 6 · Location 391

Cost per line of code over time

Note - Chapter 1 What Is Design and Architecture? > Page 6 · Location 392

More people working on THE SAME thing does not mean increased velocity

Highlight (pink) - Chapter 1 What Is Design and Architecture? > Page 10 · Location 427

“ We can clean it up later ; we just have to get to market first ! ”

Note - Chapter 1 What Is Design and Architecture? > Page 10 · Location 427

I feel this way

Highlight (yellow) - Chapter 1 What Is Design and Architecture? > Page 11 · Location 449

The only way to go fast , is to go well .

Note - Chapter 1 What Is Design and Architecture? > Page 11 · Location 450

??? how so

Highlight (blue) - Chapter 1 What Is Design and Architecture? > Page 12 · Location 455

Their overconfidence will drive the redesign into the same mess as the original project .

Highlight (yellow) - Chapter 2 A Tale of Two Values > Page 14 · Location 477

If we’d wanted the behavior of machines to be hard to change , we would have called it hardware .

Highlight (yellow) - Chapter 2 A Tale of Two Values > Page 15 · Location 488

The problem , of course , is the architecture of the system . The more this architecture prefers one shape over another , the more likely new features will be harder and harder to fit into that structure . Therefore architectures should be as shape agnostic are practical .

Highlight (pink) - Chapter 2 A Tale of Two Values > Page 16 · Location 503

If you ask the business managers if they want to be able to make changes , they’ll say that of course they do , but may then qualify their answer by noting that the current functionality is more important than any later flexibility . In contrast , if the business managers ask you for a

Highlight (yellow) - Chapter 2 A Tale of Two Values > Page 16 · Location 511

Figure 2.1 Eisenhower matrix

Highlight (pink) - Chapter 2 A Tale of Two Values > Page 18 · Location 533

developer , you are a stakeholder .

PART II Starting with the Bricks: Programming Paradigms

Highlight (yellow) - Chapter 4 Structured Programming > Page 25 · Location 595

STRUCTURED PROGRAMMING

Note - Chapter 4 Structured Programming > Page 25 · Location 596

Structured programming languages are programming languages that enforce structured programming principles, which promote the use of control structures like sequence, selection (if-else), and iteration (loops) to organize code in a clear, readable, and maintainable manner. These languages typically discourage or restrict the use of unstructured control flow mechanisms like the goto statement. When a goto statement is encountered, it transfers control to a labeled statement elsewhere in the code, bypassing normal control flow constructs like loops and conditional statements. This can lead to non-linear, unpredictable execution paths, making it hard to follow the logic of the program. goto statements can lead to unintended consequences, such as infinite loops, unreachable code, or unexpected program behavior, especially in large or complex codebases. Science does not work by proving statements true, but rather by proving statements false. Those statements that we cannot prove false, after much effort, we deem to be true enough for our purposes. According to Dijkstra, Structured programming forces us to recursively decompose a program into a set of small provable functions. We can then use tests to try to prove those small provable functions incorrect. If such tests fail to prove incorrectness, then we deem the functions to be correct enough for our purposes.

Highlight (yellow) - Chapter 4 Structured Programming > Page 31 · Location 680

Dijkstra once said , “ Testing shows the presence , not the absence , of bugs . ” In other words , a program can be proven incorrect by a test , but it cannot be proven correct . All that tests can do , after sufficient testing effort , is allow us to deem a program to be correct enough for our purposes .

Highlight (yellow) - Chapter 5 Object-Oriented Programming > Page 36 · Location 737

private : double x ; double y ; } ;

Note - Chapter 5 Object-Oriented Programming > Page 36 · Location 738

Week Encapsulation

Highlight (yellow) - Chapter 5 Object-Oriented Programming > Page 36 · Location 739

point.cc

Highlight (yellow) - Chapter 5 Object-Oriented Programming > Page 37 · Location 748

Java and C # simply abolished the header / implementation split altogether , thereby weakening encapsulation even more . In these languages , it is impossible to separate the declaration and definition of a class .

Highlight (pink) - Chapter 5 Object-Oriented Programming > Page 40 · Location 790

POLYMORPHISM ?

Note - Chapter 5 Object-Oriented Programming > Page 40 · Location 790

Next Topic

Highlight (yellow) - Chapter 5 Object-Oriented Programming > Page 44 · Location 848

OO allows the plugin architecture to be used anywhere , for anything .

Note - Chapter 5 Object-Oriented Programming > Page 44 · Location 849

polymorphism:, a fundamental concept in object-oriented programming (OOP). It first acknowledges that polymorphic behavior existed before OOP, exemplified through functions like getchar() and putchar() in C, whose behavior depended on the type of input/output device.

Highlight (orange) - Chapter 5 Object-Oriented Programming > Page 44 · Location 849

DEPENDENCY INVERSION

Note - Chapter 5 Object-Oriented Programming > Page 44 · Location 849

Every time we have this we should be refactoring to do THIS // Low-level module interface interface MessageSender { void sendMessage(String message); } class EmailSender implements MessageSender { @Override public void sendMessage(String message) { System.out.println("Sending email: " + message); } } class SMSSender implements MessageSender { @Override public void sendMessage(String message) { System.out.println("Sending SMS: " + message); } } // High-level module class MessageService { private MessageSender sender; public MessageService(MessageSender sender) { this.sender = sender; } public void send(String message) { sender.sendMessage(message); } } // Application public class Main { public static void main(String[] args) { // Using EmailSender MessageSender emailSender = new EmailSender(); MessageService emailService = new MessageService(emailSender); emailService.send("Hello, this is an email message."); // Using SMSSender MessageSender smsSender = new SMSSender(); MessageService smsService = new MessageService(smsSender); smsService.send("Hello, this is an SMS message."); }

Highlight (pink) - Chapter 5 Object-Oriented Programming > Page 47 · Location 886

is independent deployability .

Highlight (yellow) - Chapter 5 Object-Oriented Programming > Page 47 · Location 888

CONCLUSION

Note - Chapter 5 Object-Oriented Programming > Page 47 · Location 889

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.

Note - Chapter 6 Functional Programming > Page 56 · Location 1001

The chapter also discusses strategies for integrating mutability into functional programming systems, such as segregating mutable and immutable components and using transactional memory to manage mutable state safely. It introduces the concept of event sourcing as a strategy for managing state without mutable variables, emphasizing its benefits in terms of scalability and concurrency management.

Highlight (blue) - Chapter 6 Functional Programming > Page 56 · Location 1007

With that realization , we have to face an unwelcome fact : Software is not a rapidly advancing technology . The rules of software are the same today as they were in 1946 , when Alan Turing wrote the very first code that would execute in an electronic computer . The tools have changed , and the hardware has changed , but the essence of software remains the same .

PART III Design Principles

Highlight (yellow) - Chapter 7 SRP: The Single Responsibility Principle > Page 67 · Location 1139

CONCLUSION

Note - Chapter 7 SRP: The Single Responsibility Principle > Page 67 · Location 1139

There are many other symptoms that we could investigate, but they all involve multiple people changing the same source file for different reasons. Once again, the way to avoid this problem is to separate code that supports different actors.

Highlight (yellow) - Chapter 8 OCP: The Open-Closed Principle > Page 69 · Location 1143

OCP : THE OPEN - CLOSED PRINCIPLE

Note - Chapter 8 OCP: The Open-Closed Principle > Page 69 · Location 1144

SOLID is a popular set of design principles that are used in object-oriented software development. SOLID is an acronym that stands for five key design principles: single responsibility principle, open-closed principle, Liskov substitution principle, interface segregation principle, and dependency inversion principle. Ch7: SRP: THE SINGLE RESPONSIBILITY PRINCIPLE The Single Responsibility Principle (SRP) states that a class should have only one reason to change, meaning that it should have only one responsibility. When a class has multiple responsibilities, it becomes tightly coupled, less maintainable, and harder to understand. Let's illustrate this with an example: Consider a User class that is responsible for both user management (e.g., creating, updating, and deleting users) and sending notifications to users. This violates the SRP because the User class has two distinct responsibilities: user management and notification sending. public class User { private String name; private String email; private String phone; public User(String name, String email, String phone) { this.name = name; this.email = email; this.phone = phone; } public void createUser() { // Code to create a new user in the database System.out.println("User created: " + name); } public void updateUser() { // Code to update user information in the database System.out.println("User updated: " + name); } public void deleteUser() { // Code to delete user from the database System.out.println("User deleted: " + name); } public void sendNotification(String message) { // Code to send notification email to the user System.out.println("Notification sent to " + email + ": " + message); } } In this example, the User class has methods for user management (createUser, updateUser, deleteUser) and also a method for sending notifications (sendNotification). This violates the SRP because the class has more than one reason to change: it may change if user management requirements change or if notification sending requirements change. To solve this problem, we can separate the responsibilities into different classes: public class User { private String name; private String email; private String phone; public User(String name, String email, String phone) { this.name = name; this.email = email; this.phone = phone; } // Getters and setters for name and email // Methods related to user management public void createUser() { // Code to create a new user in the database System.out.println("User created: " + name); } public void updateUser() { // Code to update user information in the database System.out.println("User updated: " + name); } public void deleteUser() { // Code to delete user from the database System.out.println("User deleted: " + name); } public String getEmail() { return email; } public String getPhone() { return phone; } } public class NotificationService { public void sendNotification(User user, String message) { // Code to send notification email to the user System.out.println("Notification sent to " + user.getEmail() + ": " + message); } } In this refactored code, the User class is responsible only for user management tasks, and a new NotificationService class is responsible for sending notifications. This separation adheres to the SRP, as each class has only one responsibility and one reason to change. If user management requirements change, only the User class needs to be modified, and if notification sending requirements change, only the NotificationService class needs to be modified. => read page 82-83 to see how this problem affect whole design. There are many other symptoms that we could investigate, but they all involve multiple people changing the same source file for different reasons. Once again, the way to avoid this problem is to separate code that supports different actors. Ch8 OCP: THE OPEN-CLOSED PRINCIPLE The Open/Closed Principle (OCP) is one of the SOLID principles of object-oriented design, which encourages the code to be open for extension but closed for modification. ############## Before OCP ############## class Footballer { constructor(name, age, role) { this.name = name; this.age = age; this.role = role; } getFootballerRole() { switch (this.role) { case 'goalkeeper': console.log(The footballer, ${this.name} is a goalkeeper); break; case 'defender': console.log(The footballer, ${this.name} is a defender); break; case 'midfielder': console.log(The footballer, ${this.name} is a midfielder); break; case 'forward': console.log(The footballer, ${this.name} plays in the forward line); break; default: throw new Error(Unsupported animal type: ${this.type}); } } } const kante = new Footballer('Ngolo Kante', 31, 'midfielder'); const hazard = new Footballer('Eden Hazard', 32, 'forward'); kante.getFootballerRole(); // The footballer, Ngolo Kante is a midfielder hazard.getFootballerRole(); // The footballer, Eden Hazard plays in the forward line ############## After OCP ############## class Footballer { constructor(name, age, role) { this.name = name; this.age = age; this.role = role; } getRole() { return this.role.getRole(); } } // PlayerRole class uses the getRole method class PlayerRole { getRole() {} } // Sub classes for different roles extend the PlayerRole class class GoalkeeperRole extends PlayerRole { getRole() { return 'goalkeeper'; } } class DefenderRole extends PlayerRole { getRole() { return 'defender'; } } class MidfieldRole extends PlayerRole { getRole() { return 'midfielder'; } } class ForwardRole extends PlayerRole { getRole() { return 'forward'; } } // Putting all of them together const hazard = new Footballer('Hazard', 32, new ForwardRole()); console.log(${hazard.name} plays in the ${hazard.getRole()} line); // Hazard plays in the forward line const kante = new Footballer('Ngolo Kante', 31, new MidfieldRole()); console.log(${kante.name} is the best ${kante.getRole()} in the world!); //Ngolo Kante is the best midfielder in the world! img Erum M. 09:50 SOLID is a popular set of design principles that are used in object-oriented software development. SOLID is an acronym that stands for five key design principles: single responsibility principle, open-closed principle, Liskov substitution principle, interface segregation principle, and dependency inversion principle. Ch7: SRP: THE SINGLE RESPONSIBILITY PRINCIPLE The Single Responsibility Principle (SRP) states that a class should have only one reason to change, meaning that it should have only one responsibility. When a class has multiple responsibilities, it becomes tightly coupled, less maintainable, and harder to understand. Let's illustrate this with an example: Consider a User class that is responsible for both user management (e.g., creating, updating, and deleting users) and sending notifications to users. This violates the SRP because the User class has two distinct responsibilities: user management and notification sending. public class User { private String name; private String email; private String phone; public User(String name, String email, String phone) { this.name = name; this.email = email; this.phone = phone; } public void createUser() { // Code to create a new user in the database System.out.println("User created: " + name); } public void updateUser() { // Code to update user information in the database System.out.println("User updated: " + name); } public void deleteUser() { // Code to delete user from the database System.out.println("User deleted: " + name); } public void sendNotification(String message) { // Code to send notification email to the user System.out.println("Notification sent to " + email + ": " + message); } } In this example, the User class has methods for user management (createUser, updateUser, deleteUser) and also a method for sending notifications (sendNotification). This violates the SRP because the class has more than one reason to change: it may change if user management requirements change or if notification sending requirements change. To solve this problem, we can separate the responsibilities into different classes: public class User { private String name; private String email; private String phone; public User(String name, String email, String phone) { this.name = name; this.email = email; this.phone = phone; } // Getters and setters for name and email // Methods related to user management public void createUser() { // Code to create a new user in the database System.out.println("User created: " + name); } public void updateUser() { // Code to update user information in the database System.out.println("User updated: " + name); } public void deleteUser() { // Code to delete user from the database System.out.println("User deleted: " + name); } public String getEmail() { return email; } public String getPhone() { return phone; } } public class NotificationService { public void sendNotification(User user, String message) { // Code to send notification email to the user System.out.println("Notification sent to " + user.getEmail() + ": " + message); } } In this refactored code, the User class is responsible only for user management tasks, and a new NotificationService class is responsible for sending notifications. This separation adheres to the SRP, as each class has only one responsibility and one reason to change. If user management requirements change, only the User class needs to be modified, and if notification sending requirements change, only the NotificationService class needs to be modified. => read page 82-83 to see how this problem affect whole design. There are many other symptoms that we could investigate, but they all involve multiple people changing the same source file for different reasons. Once again, the way to avoid this problem is to separate code that supports different actors. Ch8 OCP: THE OPEN-CLOSED PRINCIPLE The Open/Closed Principle (OCP) is one of the SOLID principles of object-oriented design, which encourages the code to be open for extension but closed for modification. ############## Before OCP ############## class Footballer { constructor(name, age, role) { this.name = name; this.age = age; this.role = role; } getFootballerRole() { switch (this.role) { case 'goalkeeper': console.log(The footballer, ${this.name} is a goalkeeper); break; case 'defender': console.log(The footballer, ${this.name} is a defender); break; case 'midfielder': console.log(The footballer, ${this.name} is a midfielder); break; case 'forward': console.log(The footballer, ${this.name} plays in the forward line); break; default: throw new Error(Unsupported animal type: ${this.type}); } } } const kante = new Footballer('Ngolo Kante', 31, 'midfielder'); const hazard = new Footballer('Eden Hazard', 32, 'forward'); kante.getFootballerRole(); // The footballer, Ngolo Kante is a midfielder hazard.getFootballerRole(); // The footballer, Eden Hazard plays in the forward line ############## After OCP ############## class Footballer { constructor(name, age, role) { this.name = name; this.age = age; this.role = role; } getRole() { return this.role.getRole(); } } // PlayerRole class uses the getRole method class PlayerRole { getRole() {} } // Sub classes for different roles extend the PlayerRole class class GoalkeeperRole extends PlayerRole { getRole() { return 'goalkeeper'; } } class DefenderRole extends PlayerRole { getRole() { return 'defender'; } } class MidfieldRole extends PlayerRole { getRole() { return 'midfielder'; } } class ForwardRole extends PlayerRole { getRole() { return 'forward'; } } // Putting all of them together const hazard = new Footballer('Hazard', 32, new ForwardRole()); console.log(${hazard.name} plays in the ${hazard.getRole()} line); // Hazard plays in the forward line

Highlight (yellow) - Chapter 8 OCP: The Open-Closed Principle > Page 75 · Location 1215

CONCLUSION

Note - Chapter 8 OCP: The Open-Closed Principle > Page 75 · Location 1216

The Open/Closed Principle (OCP) is one of the SOLID principles of object-oriented design, which encourages the code to be open for extension but closed for modification.

Highlight (yellow) - Chapter 9 LSP: The Liskov Substitution Principle > Page 80 · Location 1258

VIOLATION

Note - Chapter 9 LSP: The Liskov Substitution Principle > Page 80 · Location 1259

class HelloWorld { public static void main(String[] args) { Rectangle rectangle = new Rectangle(2, 3); System.out.println("Area of rectangle: " + rectangle.getArea()); // Expect 6 Square square = new Square(2); System.out.println("Area of square: " + square.getArea()); // Expect 4 // Violation of Liskov Substitution Principle rectangle.setWidth(4); rectangle.setHeight(5); System.out.println("Area of rectangle after changing width and height: " + rectangle.getArea()); // Expect 20 square.setWidth(3); // Expected square area: 16 (4 * 4), but actual area: 20 (4 * 5) System.out.println("Area of square after changing width and height: " + square.getArea()); } } class Rectangle { protected int width; protected int height; public Rectangle(int width, int height) { this.width = width; this.height = height; } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getArea() { return width * height; } } class Square extends Rectangle { public Square(int size) { super(size, size); } @Override public void setWidth(int width) { super.setWidth(width); super.setHeight(width); } @Override public void setHeight(int height) { super.setHeight(height); super.setWidth(height); } }

Highlight (yellow) - Chapter 9 LSP: The Liskov Substitution Principle > Page 82 · Location 1296

The LSP can , and should , be extended to the level of architecture . A simple violation of substitutability , can cause a system’s architecture to be polluted with a significant amount of extra mechanisms .

Note - Chapter 9 LSP: The Liskov Substitution Principle > Page 82 · Location 1298

class Quadrilateral { protected int width; protected int height; public Quadrilateral(int width, int height) { this.width = width; this.height = height; } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getArea() { return width * height; } } class Rectangle extends Quadrilateral { public Rectangle(int width, int height) { super(width, height); } } class Square extends Quadrilateral { public Square(int size) { super(size, size); } } public class Main { public static void main(String[] args) { Quadrilateral rectangle = new Rectangle(2, 3); System.out.println("Area of rectangle: " + rectangle.getArea()); // Expect 6 Quadrilateral square = new Square(2); System.out.println("Area of square: " + square.getArea()); // Expect 4 rectangle.setWidth(4); rectangle.setHeight(5); System.out.println("Area of rectangle after changing width and height: " + rectangle.getArea()); // Expect 20 square.setWidth(4); square.setHeight(4); System.out.println("Area of square after changing width and height: " + square.getArea()); // Expect 16 } }

Highlight (yellow) - Chapter 10 ISP: The Interface Segregation Principle > Page 83 · Location 1300

ISP :

Note - Chapter 10 ISP: The Interface Segregation Principle > Page 83 · Location 1300

################## Before ISP ################## protocol PaymentMethod { func processCreditCardPayment() func processPayPalPayment() } Now, let’s create a class for a credit card payment: class CreditCardPayment: PaymentMethod { func processCreditCardPayment() { // Code to process a credit card payment } func processPayPalPayment() { // This method is not relevant for credit card payments } } In this example, the CreditCardPayment class has to implement the processPayPalPayment method even though it doesn't use it. This violates the ISP because the class is forced to have unnecessary methods.

Highlight (yellow) - Chapter 10 ISP: The Interface Segregation Principle > Page 86 · Location 1334

CONCLUSION

Note - Chapter 10 ISP: The Interface Segregation Principle > Page 86 · Location 1335

################## After ISP ################## protocol CreditCardPayment { func processCreditCardPayment() } protocol PayPalPayment { func processPayPalPayment() } Now, when implementing a credit card payment class, you only need to conform to the relevant interface: class CreditCardPaymentProcessor: CreditCardPayment { func processCreditCardPayment() { // Code to process a credit card payment } } In dynamically typed languages like Ruby and Python, such declarations don’t exist in source code. Instead, they are inferred at runtime. Thus there are no source code dependencies to force recompilation and redeployment. This is the primary reason that dynamically typed languages create systems that are more flexible and less tightly coupled than statically typed languages. This fact could lead you to conclude that the ISP is a language issue, rather than an architecture issue.

PART IV Component Principles

Highlight (yellow) - Chapter 12 Components > Page 95 · Location 1410

COMPONENTS

Note - Chapter 12 Components > Page 95 · Location 1411

The chapter "Components" provides a historical overview of the evolution of software development practices, particularly focusing on the management and organization of code components. In the early years of software development, programmers had to manage memory locations manually, which resulted in programs being tightly coupled and difficult to maintain. Functions were included directly in the application code, leading to long compile times and limited reusability. To address these challenges, the concept of relocatable binaries and linking loaders was introduced. Relocatable binaries allowed code to be loaded at different memory locations, enabling separate compilation and linking of code segments. Linking loaders helped resolve external references between code segments, improving the efficiency of the development process.

Highlight (yellow) - Chapter 13 Component Cohesion > Page 106 · Location 1558

CCP prompts

Note - Chapter 13 Component Cohesion > Page 106 · Location 1558

The CCP advocates for grouping together classes that change for the same reasons and at the same times, while separating classes that change for different reasons or at different times. Similar to the Single Responsibility Principle (SRP) for classes, the CCP ensures that components have a single, well-defined reason to change, minimizing the impact of changes on the system.

Highlight (yellow) - Chapter 13 Component Cohesion > Page 106 · Location 1566

minimal number of components .

Note - Chapter 13 Component Cohesion > Page 106 · Location 1567

Example: (CCP) Let's consider a web-based e-commerce platform as an example to illustrate the Common Closure Principle (CCP). In this system, we can identify various components that handle different aspects of the platform's functionality: User Management Component: This component manages user authentication, registration, and profile management functionalities. Classes within this component may include UserAuthenticator, UserRegistrar, and UserProfileManager. Product Catalog Component: Responsible for managing the catalog of products available for sale on the platform. Classes within this component may include Product, ProductManager, and ProductCategory. Order Processing Component: Handles the process of placing, modifying, and fulfilling orders. Classes within this component may include Order, OrderProcessor, and PaymentGateway. Recommendation System Component: Provides personalized product recommendations to users based on their browsing and purchase history. Classes within this component may include RecommendationEngine, UserPreferences, and RecommendationGenerator. Now, let's apply the Common Closure Principle: Suppose there is a new requirement to enhance the user experience by adding a feature that allows users to create and manage wishlists of products they intend to purchase later. This feature touches upon both the User Management Component and the Product Catalog Component. Following CCP: We should group together the classes related to wishlist management within a new or existing component, let's call it Wishlist Management Component. This component will include classes such as Wishlist, WishlistManager, and WishlistItem. Classes within the User Management Component remain focused on user authentication and profile management, which are separate concerns from wishlist management. Therefore, they do not need to change due to the addition of the wishlist feature. Similarly, classes within the Product Catalog Component remain focused on product catalog management and do not need to change due to the wishlist feature. By applying the Common Closure Principle, we ensure that changes related to wishlist management are contained within a single component, minimizing the impact on other parts of the system. This makes the system more maintainable and adaptable to future changes.

Highlight (pink) - Chapter 13 Component Cohesion > Page 109 · Location 1603

Figure 13.1 Cohesion principles tension diagram

Note - Chapter 13 Component Cohesion > Page 109 · Location 1604

PRINT!

Highlight (pink) - Chapter 14 Component Coupling > Page 117 · Location 1705

BREAKING THE CYCLE

Note - Chapter 14 Component Coupling > Page 117 · Location 1706

###################### ### Violating the ADP: ###################### ```java // Package A public class ClassA { private ClassB b; public ClassA() { this.b = new ClassB(); } public void methodA() { // Using ClassB b.methodB(); } } // Package B public class ClassB { private ClassA a; public ClassB() { this.a = new ClassA(); } public void methodB() { // Using ClassA a.methodA(); } } ``` ############################# ### Solution to Address ADP: ############################# One solution to address the cyclic dependency is to introduce an intermediary interface or abstract class that both `ClassA` and `ClassB` depend on. This breaks the cycle and adheres to the ADP. ```java // Package A public class ClassA { private InterfaceX x; public ClassA(InterfaceX x) { this.x = x; } public void methodA() { // Using InterfaceX x.methodX(); } } // Package B public class ClassB { private InterfaceX x; public ClassB(InterfaceX x) { this.x = x; } public void methodB() { // Using InterfaceX x.methodX(); } } // Interface for dependency public interface InterfaceX { void methodX(); } ```

Highlight (yellow) - Chapter 14 Component Coupling > Page 119 · Location 1736

volatile components .

Note - Chapter 14 Component Coupling > Page 119 · Location 1737

Ideally we separate the Volatile components

Highlight (yellow) - Chapter 14 Component Coupling > Page 119 · Location 1739

If we tried to design the component dependency structure before we designed any classes , we would likely fail rather badly . We would not know much about common closure , we would be unaware of any reusable elements , and we would almost certainly create components that produced dependency cycles . Thus the component dependency structure grows and evolves with the logical design of the system .

Note - Chapter 14 Component Coupling > Page 119 · Location 1743

Dont make this at the begining of the project. This module start to pop up as the system grows

Highlight (yellow) - Chapter 14 Component Coupling > Page 122 · Location 1775

• Fan - in :

Note - Chapter 14 Component Coupling > Page 122 · Location 1775

Things that are implmeneting ME

Highlight (yellow) - Chapter 14 Component Coupling > Page 122 · Location 1776

Fan - out :

Note - Chapter 14 Component Coupling > Page 122 · Location 1777

Components that I implment

Highlight (yellow) - Chapter 14 Component Coupling > Page 122 · Location 1778

= Fan - out , ( Fan - in + Fan - out ) .

Note - Chapter 14 Component Coupling > Page 122 · Location 1779

Out / ( IN + OUT)

Highlight (yellow) - Chapter 14 Component Coupling > Page 122 · Location 1784

Let’s say we want to calculate the stability of the component Cc . We find that there are three classes outside Cc that depend on classes in Cc . Thus , Fan - in = 3 . Moreover , there is one class outside Cc that classes in Cc depend on . Thus , Fan - out = 1 and I = 1 / 4 .

Note - Chapter 14 Component Coupling > Page 122 · Location 1787

Results is 0.25 (which is closer to 0 Max Stable) so its getting there!

Highlight (yellow) - Chapter 14 Component Coupling > Page 123 · Location 1796

The SDP says that the I metric of a component should be larger than the I metrics of the components that it depends on . That is , I metrics should decrease in the direction of dependency .

Note - Chapter 14 Component Coupling > Page 123 · Location 1798

Essentially is a tree that you want to get to 0 the deeper the level it is

Highlight (orange) - Chapter 14 Component Coupling > Page 124 · Location 1805

Figure 14.8 An ideal configuration for a system with three components

Note - Chapter 14 Component Coupling > Page 124 · Location 1806

Like this! I > of I dependants The SDP says that the I metric of a component should be larger than the I metrics of the components that it depends on. That is, I metrics should decrease in the direction of dependency.

Highlight (yellow) - Chapter 14 Component Coupling > Page 124 · Location 1808

Figure 14.9 SDP violation

Note - Chapter 14 Component Coupling > Page 124 · Location 1809

You count everything!!! not just 1 level above

Highlight (pink) - Chapter 14 Component Coupling > Page 126 · Location 1831

THE STABLE ABSTRACTIONS PRINCIPLE

Note - Chapter 14 Component Coupling > Page 126 · Location 1832

Before Applying SAP: class CustomerDatabase { void addCustomer(Customer customer) { // Direct implementation for adding a customer to a database } } This class is concrete and directly implements the addition of a customer to a database. If the way customers are added to the database changes, this class needs to be modified, affecting all dependents. After Applying SAP: interface ICustomerDatabase { void addCustomer(Customer customer); } class CustomerDatabase implements ICustomerDatabase { public void addCustomer(Customer customer) { // Implementation for adding a customer to a database } }

Highlight (pink) - Chapter 14 Component Coupling > Page 130 · Location 1905

• D3 : Distance . D = | A + I – 1 | .

Highlight (pink) - Chapter 14 Component Coupling > Page 132 · Location 1924

CONCLUSION

PART V Architecture

Highlight (yellow) - Chapter 15 What Is Architecture? > Page 135 · Location 1936

ARCHITECTURE ?

Note - Chapter 15 What Is Architecture? > Page 135 · Location 1937

Example Code: Before and After Applying Architecture Principles Before: A monolithic application where changes impact the entire system, leading to difficult deployments and slow feature development. A monolithic application is a software application that is built as a single, indivisible unit where all components and functionalities are tightly integrated and deployed together. In a monolithic architecture, the entire application, including the user interface, business logic, and data access layer, is developed, deployed, and scaled as a single unit. After: Refactoring the application into a microservices architecture, where each service can be developed, deployed, and scaled independently. This transformation demonstrates the value of architectural decisions in facilitating easier maintenance, deployment, and the ability to adapt to changing requirements.

Highlight (yellow) - Chapter 15 What Is Architecture? > Page 136 · Location 1946

The architecture of a software system is the shape given to that system by those who build it . The form of that shape is in the division of that system into components , the arrangement of those components , and the ways in which those components communicate with each other .

Highlight (yellow) - Chapter 15 What Is Architecture? > Page 136 · Location 1950

The strategy behind that facilitation is to leave as many options open as possible , for as long as possible .

Note - Chapter 15 What Is Architecture? > Page 136 · Location 1951

Consider a web application that experiences performance issues due to inefficient database queries and poor caching strategies. Initially, the application struggles to handle concurrent user requests, resulting in slow response times and occasional downtime. To address these operational difficulties, the operations team might suggest increasing the server's memory, adding more CPU cores, or deploying additional instances of the application to distribute the load. By investing in more hardware resources, the system's operational performance might improve, and users may experience fewer disruptions. However, simply adding more hardware does not fundamentally resolve the underlying architectural issues causing the performance problems. The inefficient database queries and caching strategies remain, potentially leading to continued scalability challenges and increased operational costs over time.

Highlight (pink) - Chapter 15 What Is Architecture? > Page 142 · Location 2038

A good architect maximizes the number of decisions not made .

Note - Chapter 15 What Is Architecture? > Page 142 · Location 2038

THIS!

Highlight (yellow) - Chapter 15 What Is Architecture? > Page 143 · Location 2067

change . The Open – Closed Principle was born ( but not yet named ) .

Note - Chapter 15 What Is Architecture? > Page 143 · Location 2068

Interesting

Note - Chapter 16 Independence > Page 147 · Location 2118

Key points: - Decoupling - Keeping options open for scale Horizontal - Horizontal code : UI / DB / Business Logic - Veritical code: Lets decouple useCase Independence is crucial for several reasons: - Flexibility: It allows for parts of the system to be changed, upgraded, or replaced without requiring a complete overhaul. - Maintainability: Independent components are easier to understand, test, and maintain. - Scalability: Systems designed for independence can more easily be scaled up or out to meet increasing demand.

Highlight (yellow) - Chapter 16 Independence > Page 149 · Location 2149

Any organization that designs a system will produce a design whose structure is a copy of the organization’s communication structure .

Note - Chapter 16 Independence > Page 149 · Location 2150

How so?

Highlight (pink) - Chapter 16 Independence > Page 150 · Location 2155

“ immediate deployment . ” A

Highlight (pink) - Chapter 16 Independence > Page 150 · Location 2162

The reality is that achieving this balance is pretty hard .

Highlight (pink) - Chapter 16 Independence > Page 151 · Location 2168

A good architecture makes the system easy to change , in all the ways that it must change , by leaving options open .

Highlight (yellow) - Chapter 16 Independence > Page 152 · Location 2182

The database , the query language ,

Note - Chapter 16 Independence > Page 152 · Location 2182

Can be decupled

Highlight (pink) - Chapter 16 Independence > Page 152 · Location 2183

business rules

Highlight (blue) - Chapter 16 Independence > Page 152 · Location 2183

the UI .

Highlight (yellow) - Chapter 16 Independence > Page 152 · Location 2190

narrow vertical slices that cut through the horizontal layers of the system .

Highlight (yellow) - Chapter 16 Independence > Page 152 · Location 2197

database , then adding new use cases will be unlikely to affect older ones .

Note - Chapter 16 Independence > Page 152 · Location 2197

You also need to decouple use cases

Highlight (yellow) - Chapter 16 Independence > Page 153 · Location 2207

service - oriented architecture .

Highlight (yellow) - Chapter 16 Independence > Page 153 · Location 2212

INDEPENDENT DEVELOP - ABILITY

Note - Chapter 16 Independence > Page 153 · Location 2212

Example of independent systems

Highlight (pink) - Chapter 16 Independence > Page 154 · Location 2222

DUPLICATION

Note - Chapter 16 Independence > Page 154 · Location 2222

True Duplication: python def calculate_area(length, width): return length * width Duplicate code for calculating area of different rooms area_living_room = calculate_area(5, 3) area_kitchen = calculate_area(5, 3) ..... room_dimensions = (5, 3) area_living_room = calculate_area(*room_dimensions) area_kitchen = calculate_area(*room_dimensions)

Highlight (yellow) - Chapter 16 Independence > Page 154 · Location 2228

then they are not true duplicates .

Note - Chapter 16 Independence > Page 155 · Location 2228

Accidental duplication due to similar business logic in different functions def process_discount(order): if order.customer.is_vip: return order.total * 0.8 # 20% discount else: return order.total def process_shipping(order): if order.customer.is_vip: return 0 # Free shipping for VIP else: return 5.99 # Standard shipping cost .... To remove accidental duplication, you can abstract the common concept (VIP check) and reuse the logic: def is_vip_customer(customer): return customer.is_vip def process_discount(order): return order.total * 0.8 if is_vip_customer(order.customer) else order.total def process_shipping(order): return 0 if is_vip_customer(order.customer) else 5.99

Note - Chapter 16 Independence > Page 158 · Location 2278

https://merlino.agency/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fvsall43tabcn%2FgyZteBML1XipqwnZTPzRJ%2F0ad14b0e2271d7797e92791b66689ff3%2FClean_Architecture.jpeg&w=1920&q=75

Highlight (pink) - Chapter 16 Independence > Page 158 · Location 2278

Yes , this is tricky .

Highlight (yellow) - Chapter 17 Boundaries: Drawing Lines > Page 165 · Location 2380

WHICH LINES DO YOU DRAW , AND WHEN DO YOU DRAW THEM ?

Note - Chapter 17 Boundaries: Drawing Lines > Page 165 · Location 2380

let's conceptualize a simple scenario for company P: Without proper boundaries (company P's initial problem): # GUI, business logic, and database access are intermingled class Order: def save_order(self, order_data): # Database access code mixed with business logic # Serialize and send data across tiers pass Refactoring to add boundaries (what company P could have done): # Business logic layer class OrderService: def save_order(self, order_data): # Business logic for saving orders database.save(order_data) # Use a simple database interface # Data access layer class Database: @staticmethod def save(order_data): # Code to save the order to the database pass

Highlight (yellow) - Chapter 17 Boundaries: Drawing Lines > Page 167 · Location 2399

Figure 17.2 The boundary line

Note - Chapter 17 Boundaries: Drawing Lines > Page 167 · Location 2399

Purpose of Boundaries: Boundaries are drawn to keep software elements distinct and separate so that changes in one area do not force changes in another. They make the system more understandable and organize the development effort into smaller, more manageable parts. Levels of Boundaries: Boundaries can exist at multiple levels within a system—between methods, classes, components, and even services. Drawing Boundaries: The act of drawing a boundary involves making decisions about what to keep inside and what to exclude. This often means deciding which data and behavior are public and which are private. Crossing Boundaries: The passage across a boundary from one realm to another should be carefully managed, typically through the use of interfaces or abstract classes that define a contract.

Highlight (yellow) - Chapter 17 Boundaries: Drawing Lines > Page 173 · Location 2467

CONCLUSION

Note - Chapter 17 Boundaries: Drawing Lines > Page 173 · Location 2467

# UI layer class UserInterface: def show(self): data = BusinessLogic().process_data() print(f"Displaying data: {data}") # Business rules layer class BusinessLogic: def process_data(self): data = DataAccess().get_data() # Process data according to business rules return data * 10 # Data access layer class DataAccess: def get_data(self): return 5 # Simplified for example purposes In this code example, the UserInterface class does not need to know how the data is processed or where it comes from. The BusinessLogic class does not need to know about the data source's specifics. Each class represents a boundary, and the crossing of these boundaries is handled through method calls, keeping the concerns separated. ############################### ##################################### Code Example without Boundaries: Here is an example of code where the boundaries between the database, business rules, and GUI are not properly maintained: # A simple application where boundaries are not well defined. class Order: def __init__(self, order_data): self.order_data = order_data self.database = Database() # Database instance created within the business object def calculate_total(self): # Business logic intermingled with database access prices = self.database.get_prices(self.order_data['items']) total = sum(prices) self.display_total(total) # GUI logic within the business logic return total def display_total(self, total): # GUI logic inside the business object print(f"The total order cost is: ${total}") class Database: def get_prices(self, items): # Directly querying the database # In reality, this would be a database call to fetch the prices based on item IDs return [9.99, 12.99, 7.49] # Returning hardcoded prices for simplicity # Usage order_data = {'items': [1, 2, 3]} order = Order(order_data) order.calculate_total() ############################ Refactoring to Establish Boundaries: To introduce proper boundaries between the database, business rules, and GUI, we can refactor the code by defining clear interfaces and separating responsibilities into different layers: # Business Logic Layer class Order: def __init__(self, order_data, data_source): self.order_data = order_data self.data_source = data_source def calculate_total(self): # Business logic only concerns itself with calculations prices = self.data_source.get_prices(self.order_data['items']) total = sum(prices) return total # Data Access Layer class Database: def get_prices(self, items): # Responsible only for database access return [9.99, 12.99, 7.49] # Returning hardcoded prices for simplicity # Presentation/GUI Layer class OrderGUI: def __init__(self, order): self.order = order def display_total(self): total = self.order.calculate_total() print(f"The total order cost is: ${total}") # Usage order_data = {'items': [1, 2, 3]} database = Database() order = Order(order_data, database) order_gui = OrderGUI(order) order_gui.display_total() In this refactored example, the Order class represents the business rules and only deals with calculating the order total. The Database class is the data access layer responsible for interacting with the database. The OrderGUI class handles the user interface and calls the display_total method to show the total order cost. Each class now has a single responsibility, and the boundaries between database access, business logic, and GUI are clear. #############################

Highlight (yellow) - Chapter 18 Boundary Anatomy > Page 176 · Location 2480

BOUNDARY CROSSING

Note - Chapter 18 Boundary Anatomy > Page 176 · Location 2481

Module Boundaries Example: Module boundaries are internal to the application and typically involve organizing code into packages, modules, or namespaces. Here's an example in Python that separates billing and customers into different modules. billing.py: # billing.py - A module for billing-related operations class InvoiceProcessor: def create_invoice(self, order): # ... logic to create an invoice ... pass customers.py: # customers.py - A module for customer-related operations class CustomerProfile: def get_customer_data(self, customer_id): # ... logic to fetch customer data ... pass main.py: # main.py - Main application module that uses both billing and customers modules from billing import InvoiceProcessor from customers import CustomerProfile invoice_processor = InvoiceProcessor() customer_profile = CustomerProfile() Here we can use both modules, while keeping their internal logic separate.

Highlight (yellow) - Chapter 18 Boundary Anatomy > Page 178 · Location 2518

DEPLOYMENT COMPONENTS

Note - Chapter 18 Boundary Anatomy > Page 178 · Location 2519

Deployment Boundaries Example: Deployment boundaries are about how parts of the system are deployed independently, such as microservices communicating over a network. Here's an example of a simple HTTP API acting as a boundary: order_service.py (Deployed as a microservice): # order_service.py - Microservice for handling orders from flask import Flask, jsonify, request app = Flask(__name__) @app.route('/create_order', methods=['POST']) def create_order(): # Logic to create an order order_data = request.json # ... create order logic ... return jsonify({"status": "success"}) if __name__ == '__main__': app.run(port=5000) billing_service.py (A separate microservice that calls the order service): # billing_service.py - Microservice for handling billing import requests def create_order_in_order_service(order_data): response = requests.post('http://order_service:5000/create_order', json=order_data) return response.json() # Assume order_data is provided, create an order by calling the order service order_data = {"customer_id": 123, "items": [{"sku": "ABC", "quantity": 1}]} create_order_response = create_order_in_order_service(order_data)

Note - Chapter 18 Boundary Anatomy > Page 181 · Location 2554

Example without Boundary Management**: # Code tightly coupled without boundary management class CustomerData: def get_customer_data(self, customer_id): # Implementation directly accessing the database return database.query(f"SELECT * FROM customers WHERE id = {customer_id}") class OrderProcessing: def process_order(self, order_id, customer_id): customer_data = CustomerData().get_customer_data(customer_id) # Processing the order with direct dependency on CustomerData # ... additional order processing logic ... ------ Example with Boundary Management**: # Code with proper boundary management class CustomerDataInterface: def get_customer_data(self, customer_id): pass class CustomerDataImplementation(CustomerDataInterface): def get_customer_data(self, customer_id): # Implementation accessing the database through an interface return database.query(f"SELECT * FROM customers WHERE id = {customer_id}") class OrderProcessing: def __init__(self, customer_data_interface): self.customer_data_interface = customer_data_interface def process_order(self, order_id, customer_id): customer_data = self.customer_data_interface.get_customer_data(customer_id) # Process the order without direct dependency on the concrete implementation # ... additional order processing logic ...

Highlight (pink) - Chapter 18 Boundary Anatomy > Page 181 · Location 2558

This means that the boundaries in a system will often be a mixture of local chatty boundaries and boundaries that are more concerned with latency .

Note - Chapter 18 Boundary Anatomy > Page 182 · Location 2559

🤔

Highlight (yellow) - Chapter 19 Policy and Level > Page 187 · Location 2617

Lower - level components should plug in to higher - level components

Note - Chapter 19 Policy and Level > Page 187 · Location 2618

High-Level Policy Component (Encryption Logic) class Encryptor: def init(self, reader, writer): self.reader = reader self.writer = writer def encrypt(self): while True: char = self.reader.read_char() if char == '': break encrypted_char = self._translate(char) self.writer.write_char(encrypted_char) def _translate(self, char): # Placeholder for encryption logic return char Low-Level Policy Components (IO Operations) class CharReader: def read_char(self): # Read a character from the input source (e.g., console, file, etc.) pass class CharWriter: def write_char(self, char): # Write a character to the output destination (e.g., console, file, etc.) pass Concrete Implementations for Console IO class ConsoleReader(CharReader): def read_char(self): return input("Enter a character to encrypt (or press enter to exit): ") class ConsoleWriter(CharWriter): def write_char(self, char): print(f"Encrypted character: {char}") Client Code reader = ConsoleReader() writer = ConsoleWriter() encryptor = Encryptor(reader, writer) encryptor.encrypt()

Highlight (yellow) - Chapter 20 Business Rules > Page 192 · Location 2666

Example use case

Note - Chapter 20 Business Rules > Page 192 · Location 2667

High-level business rule encapsulated in an entity class Account: def init(self, account_id, balance=0): self.account_id = account_id self.balance = balance def deposit(self, amount): if amount <= 0: raise ValueError("Deposit amount must be positive") self.balance += amount def withdraw(self, amount): if amount <= 0: raise ValueError("Withdrawal amount must be positive") if amount > self.balance: raise ValueError("Insufficient funds") self.balance -= amount Application business rule encapsulated in an interactor class AccountService: def init(self, account_repository): self.account_repository = account_repository def transfer(self, source_id, destination_id, amount): source_account = self.account_repository.find_by_id(source_id) destination_account = self.account_repository.find_by_id(destination_id) source_account.withdraw(amount) destination_account.deposit(amount) self.account_repository.update(source_account) self.account_repository.update(destination_account)

Highlight (yellow) - Chapter 20 Business Rules > Page 194 · Location 2697

should be the most independent and reusable code in the system .

Note - Chapter 20 Business Rules > Page 194 · Location 2698

Interesting

Highlight (yellow) - Chapter 21 Screaming Architecture > Page 195 · Location 2701

SCREAMING ARCHITECTURE

Note - Chapter 21 Screaming Architecture > Page 195 · Location 2702

Screaming architecture says WHO I AM

Highlight (yellow) - Chapter 21 Screaming Architecture > Page 196 · Location 2713

THE THEME OF AN ARCHITECTURE

Note - Chapter 21 Screaming Architecture > Page 196 · Location 2714

/library-management-system/ /cataloging/ /services/ - BookCheckoutService.java - BookReturnService.java - InventoryManagement.java /repositories/ - BookRepository.java /search/ - BookSearch.java /members/ /services/ - MemberRegistrationService.java - MembershipRenewalService.java /repositories/ - MemberRepository.java /interactions/ - BorrowBook.java - ReturnBook.java // Example service within the cataloging module public class BookCheckoutService { private final BookRepository bookRepository; public BookCheckoutService(BookRepository bookRepository) { this.bookRepository = bookRepository; } public void checkoutBook(String bookId, String memberId) { // Business logic to checkout a book to a member }

Highlight (yellow) - Chapter 21 Screaming Architecture > Page 198 · Location 2744

TESTABLE ARCHITECTURES

Note - Chapter 21 Screaming Architecture > Page 198 · Location 2745

java // Entity object public class Customer { private String customerId; private String name; // Constructors, getters, and setters } // Use case object public class CustomerRegistration { private CustomerRepository customerRepository; public CustomerRegistration(CustomerRepository customerRepository) { this.customerRepository = customerRepository; } public void registerNewCustomer(String customerId, String name) { Customer newCustomer = new Customer(customerId, name); customerRepository.save(newCustomer); } } // Interface for the repository public interface CustomerRepository { void save(Customer customer); } // Test public class CustomerRegistrationTest { @Test public void testCustomerRegistration() { // Arrange CustomerRepository fakeRepository = new FakeCustomerRepository(); CustomerRegistration customerRegistration = new CustomerRegistration(fakeRepository); // Act customerRegistration.registerNewCustomer("123", "John Doe"); // Assert assertTrue(fakeRepository.containsCustomer("123")); } } // Fake repository for testing public class FakeCustomerRepository implements CustomerRepository { private Map<String, Customer> storage = new HashMap<>(); @Override public void save(Customer customer) { storage.put(customer.getCustomerId(), customer); } public boolean containsCustomer(String customerId) { return storage.containsKey(customerId); } }

Note - Chapter 21 Screaming Architecture > Page 199 · Location 2750

Use Case Centric: A testable architecture centers around use cases, which represent the core functionalities of the application, independent of external frameworks and infrastructures. Framework Independence: While frameworks are used to facilitate certain technical aspects like persistence or web interactions, they should not be tightly integrated with the use cases or the business rules. They should be treated as tools that can be easily swapped out or removed, not as the core of the application. Entity Objects: Entities should be simple objects (sometimes called POJOs - Plain Old Java Objects, or POCOs - Plain Old CLR Objects) without any dependencies on external systems. They encapsulate business data and can be instantiated and used in tests without needing any framework setup. Use Case Objects: Use case objects contain the business logic that operates on entities. They should be designed to allow for direct input of entities and direct observation of their behaviors and outcomes, making them easily testable. Testable In Situ: This means that the use cases and entities should be testable in their natural state, without needing to set up a database, web server, or any other part of the infrastructure. Mocking/Stubs: To achieve isolation from frameworks and databases, use mocking or stubbing techniques to simulate interactions with these external components.

Highlight (yellow) - Chapter 22 The Clean Architecture > Page 202 · Location 2769

Independent of frameworks .

Note - Chapter 22 The Clean Architecture > Page 202 · Location 2769

Samething as screaming architecture

Highlight (yellow) - Chapter 22 The Clean Architecture > Page 203 · Location 2780

The clean architecture

Note - Chapter 22 The Clean Architecture > Page 203 · Location 2781

########################################## Example code for Figure 22.1 Certainly! Let's take the example of a simple banking application to illustrate the Clean Architecture model depicted in Figure 22.1. Entities (Enterprise Business Rules): # entities.py class Account: def __init__(self, account_id, balance=0): self.account_id = account_id self.balance = balance def deposit(self, amount): if amount <= 0: raise ValueError("Amount must be positive") self.balance += amount def withdraw(self, amount): if amount <= 0: raise ValueError("Amount must be positive") if amount > self.balance: raise ValueError("Insufficient funds") self.balance -= amount ------------------ Use Cases (Application Business Rules): # use_cases.py class AccountService: def __init__(self, repository): self.repository = repository def transfer(self, source_id, destination_id, amount): source_account = self.repository.find(source_id) destination_account = self.repository.find(destination_id) source_account.withdraw(amount) destination_account.deposit(amount) self.repository.update(source_account) self.repository.update(destination_account) ------------- Interface Adapters (Controllers, Presenters, Gateways): # interface_adapters.py class AccountController: def __init__(self, account_service): self.account_service = account_service def transfer_funds(self, request): self.account_service.transfer(request['source_id'], request['destination_id'], request['amount']) return True class AccountPresenter: def present(self, account): # Converts internal account data to a dictionary for external use (e.g., JSON) return { 'account_id': account.account_id, 'balance': account.balance } -------------- Frameworks & Drivers (UI, Databases, etc.): # frameworks_and_drivers.py class FlaskApp: def __init__(self, account_controller): self.account_controller = account_controller self.app = Flask(__name__) self.setup_routes() def setup_routes(self): @self.app.route('/transfer', methods=['POST']) def transfer(): data = request.get_json() success = self.account_controller.transfer_funds(data) return jsonify({'success': success}), 200 if success else 400 class SqlAccountRepository: def find(self, account_id): # SQL code to retrieve an account from the database pass def update(self, account): # SQL code to update an account in the database pass Example Usage: # main.py if __name__ == '__main__': repository = SqlAccountRepository() account_service = AccountService(repository) account_controller = AccountController(account_service) flask_app = FlaskApp(account_controller) flask_app.app.run(debug=True) In this code example: Account is an entity class that encapsulates the core business logic around a bank account. AccountService is a use case interactor that orchestrates the process of transferring funds between accounts. AccountController and AccountPresenter are interface adapters that bridge the gap between the use case interactors and the web framework. FlaskApp is part of frameworks & drivers, which adapts the HTTP requests and routes them through to the use cases. SqlAccountRepository is also a framework & driver component, dealing with the database interactions. The use of Flask, SQL, or any other specific framework is abstracted away from the core logic, adhering to the principles of Clean Architecture, where the focus is on the business rules and use cases rather than on the technology stack. ##########################################

Highlight (pink) - Chapter 22 The Clean Architecture > Page 205 · Location 2821

There’s no rule that says you must always have just these four .

Note - Chapter 22 The Clean Architecture > Page 206 · Location 2821

Good to know

Highlight (pink) - Chapter 22 The Clean Architecture > Page 208 · Location 2861

This leaves the View with almost nothing to do other than to move the data from the ViewModel into the HTML page . Note the directions of the dependencies . All dependencies cross the boundary lines pointing inward , following the Dependency Rule .

Note - Chapter 22 The Clean Architecture > Page 208 · Location 2861

Following DIP rules

Highlight (yellow) - Chapter 22 The Clean Architecture > Page 209 · Location 2864

Conforming to these simple rules is not difficult , and it will save you a lot of headaches going forward . By separating the software into layers and conforming to the Dependency Rule , you will create a system that is intrinsically testable , with all the benefits that implies . When any of the external parts of the system become obsolete , such as the database , or the web framework , you can replace those obsolete elements with a minimum of fuss .

Note - Chapter 22 The Clean Architecture > Page 210 · Location 2868

THIS.

Highlight (yellow) - Chapter 23 Presenters and Humble Objects > Page 212 · Location 2874

The Humble Object

Note - Chapter 23 Presenters and Humble Objects > Page 212 · Location 2874

Simple ones to test -- Decoupled from UI / DB

Highlight (yellow) - Chapter 23 Presenters and Humble Objects > Page 212 · Location 2881

PRESENTERS AND VIEWS

Note - Chapter 23 Presenters and Humble Objects > Page 212 · Location 2882

Entity class Order: def init(self, order_details): self.order_details = order_details # ... other business logic ... Use Case Interactor class OrderInteractor: def init(self, repository, presenter): self.repository = repository self.presenter = presenter def get_order_details(self, order_id): order = self.repository.get_order_by_id(order_id) self.presenter.present(order) Presenter class OrderPresenter: def present(self, order): # Format the order details for the UI view_model = {'order_id': order.order_details.order_id, 'total_price': order.order_details.total_price} # ... other presentation logic ... return view_model View (Could be a web page, mobile app screen, etc.) def render_order_view(view_model): # Render the order details in the UI print(f"Order ID: {view_model['order_id']}") print(f"Total Price: {view_model['total_price']}") Example Usage order_presenter = OrderPresenter() order_interactor = OrderInteractor(order_repository=SomeRepository(), presenter=order_presenter) order_details_view_model = order_interactor.get_order_details(order_id=123) render_order_view(order_details_view_model)

Highlight (yellow) - Chapter 23 Presenters and Humble Objects > Page 214 · Location 2900

DATABASE GATEWAYS

Note - Chapter 23 Presenters and Humble Objects > Page 214 · Location 2901

gateway_interface.py class AccountGateway: def get_account_balance(self, account_id): pass def update_account_balance(self, account_id, new_balance): pass interactor.py class TransferFundsInteractor: def init(self, account_gateway): self.account_gateway = account_gateway def transfer(self, source_id, destination_id, amount): source_balance = self.account_gateway.get_account_balance(source_id) destination_balance = self.account_gateway.get_account_balance(destination_id) # Business logic to transfer funds self.account_gateway.update_account_balance(source_id, source_balance - amount) self.account_gateway.update_account_balance(destination_id, destination_balance + amount) database_gateway.py class SqlAccountGateway(AccountGateway): def get_account_balance(self, account_id): # Use SQL to retrieve the account balance pass def update_account_balance(self, account_id, new_balance): # Use SQL to update the account balance pass

Highlight (yellow) - Chapter 23 Presenters and Humble Objects > Page 214 · Location 2910

DATA MAPPERS

Highlight (yellow) - Chapter 23 Presenters and Humble Objects > Page 215 · Location 2919

SERVICE LISTENERS

Note - Chapter 23 Presenters and Humble Objects > Page 215 · Location 2919

Let's consider an application that needs to interact with a payment service: Simple data structure used for communication with the payment service class PaymentRequest: def init(self, amount, payee_id, payer_id): self.amount = amount self.payee_id = payee_id self.payer_id = payer_id Service listener that handles outgoing communication class PaymentServiceSender: def send_payment_request(self, payment_request): # Formats the PaymentRequest into JSON and sends it to the payment service pass Service listener that handles incoming data class PaymentServiceListener: def receive_payment_response(self, response_data): # Parses the response data and formats it into a simple data structure payment_response = parse_response(response_data) return payment_response

Note - Chapter 23 Presenters and Humble Objects > Page 215 · Location 2925

Some objetcs are HARD, but thease are Global Scopped usually coupled with DB interectations and or views

Highlight (yellow) - Chapter 24 Partial Boundaries > Page 219 · Location 2961

The Strategy pattern

Note - Chapter 24 Partial Boundaries > Page 219 · Location 2961

Strategy Pattern The Strategy pattern is a behavioral design pattern that enables selecting an algorithm's behavior at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.

Highlight (pink) - Chapter 24 Partial Boundaries > Page 219 · Location 2962

architectural boundary .

Note - Chapter 24 Partial Boundaries > Page 219 · Location 2962

Strategy Interface class PaymentStrategy: def pay(self, amount): pass Concrete Strategies class CreditCardPayment(PaymentStrategy): def pay(self, amount): print(f"Paying {amount} using CreditCard") class PayPalPayment(PaymentStrategy): def pay(self, amount): print(f"Paying {amount} using PayPal") Context class Checkout: def init(self, payment_strategy: PaymentStrategy): self.payment_strategy = payment_strategy def execute_payment(self, amount): self.payment_strategy.pay(amount) Client if name == "main": # Client chooses the strategy at runtime payment_method = CreditCardPayment() # Or PayPalPayment() checkout = Checkout(payment_method) checkout.execute_payment(100)

Highlight (yellow) - Chapter 24 Partial Boundaries > Page 219 · Location 2963

It should also be clear that the separation can degrade pretty rapidly , as shown by the nasty dotted arrow in the diagram . Without reciprocal interfaces , nothing prevents this kind of backchannel other than the diligence and discipline of the developers and architects .

Note - Chapter 24 Partial Boundaries > Page 219 · Location 2965

Benefits of the Strategy Pattern: Flexibility and Reusability: Algorithms are encapsulated separately from the clients that use them, allowing for easy swapping and reusability of different behaviors. Decoupling: Clients are decoupled from the implementation details of the algorithms or behaviors they use. Simplicity: Simplifies conditional statements and controls the logic of selecting appropriate algorithms.

Note - Chapter 24 Partial Boundaries > Page 220 · Location 2966

Fa Sa Ds

Highlight (pink) - Chapter 24 Partial Boundaries > Page 220 · Location 2969

The Facade pattern

Note - Chapter 24 Partial Boundaries > Page 220 · Location 2970

The Facade pattern is a structural design pattern that provides a simplified interface to a complex subsystem. Its primary goal is to hide the complexity of a system, library, or framework by providing a simpler interface to the clients, while still allowing them access to the full functionalities of the underlying system.

Note - Chapter 24 Partial Boundaries > Page 220 · Location 2970

Example of the Facade Pattern: Suppose you have a complex system for managing media playback that involves components for video decoding, audio processing, and network streaming. Instead of requiring the client code to interact with all these components individually, you could create a MediaFacade class that encapsulates these operations: class VideoDecoder: def decode(self, video_data): # Complex decoding process pass class AudioProcessor: def process(self, audio_data): # Complex audio processing pass class NetworkStreamer: def stream(self, address): # Complex streaming protocol pass class MediaFacade: def init(self): self.video_decoder = VideoDecoder() self.audio_processor = AudioProcessor() self.network_streamer = NetworkStreamer() def play_video(self, video_data, address): self.video_decoder.decode(video_data) self.audio_processor.process(video_data) self.network_streamer.stream(address) print("Playing video...") vvv Client code media_facade = MediaFacade() media_facade.play_video("video data", "")

Note - Chapter 24 Partial Boundaries > Page 220 · Location 2973

Both patterns can be employed to introduce partial boundaries where a full boundary (complete separation into independently deployable units) isn't yet justified or necessary: Strategy for Business Logic: The Strategy pattern can be used to create boundaries around business logic, making it easy to switch out the logic as business rules evolve without impacting other parts of the system. Facade for External Interactions: The Facade pattern can be used to create boundaries around external interactions, like database access or third-party service integration, allowing the rest of the application to remain unaffected by changes in these external components.

Highlight (pink) - Chapter 25 Layers and Boundaries > Page 230 · Location 3084

1 . It should be just as clear that we would not apply the clean architecture approach to something as trivial as this game . After all , the entire program can probably be written in 200 lines of code or less . In this case , we’re using a simple program as a proxy for a much larger system with significant architectural boundaries .

Note - Chapter 26 The Main Component > Page 237 · Location 3165

public class Main { public static void main(String[] args) { DependencyInjector injector = new DependencyInjector(); Application application = injector.initializeApplication(); application.start(); } } Dependency Injection: public class DependencyInjector { public Application initializeApplication() { Database database = new MySqlDatabase(); UserRepository userRepository = new UserRepository(database); UserService userService = new UserService(userRepository); return new Application(userService); } } Hunt the Wumpus Example: The chapter highlights how Main loads configurations, such as language-specific strings, that should not be known by the rest of the application. public class Main implements GameMessageReceiver { private static Game game; public static void main(String[] args) { // Set up the game configurations and start the game loop game = new HuntTheWumpusGame(new EnglishLanguageConfig()); game.start(); } }

Highlight (pink) - Chapter 26 The Main Component > Page 237 · Location 3168

For example , you could have a Main plugin for Dev , another for Test , and yet another for Production . You could also have a Main plugin for each country you deploy to , or each jurisdiction , or each customer .

Highlight (yellow) - Chapter 27 Services: Great and Small > Page 239 · Location 3173

SERVICES : GREAT AND SMALL

Note - Chapter 27 Services: Great and Small > Page 239 · Location 3174

Services are NOT the architecture

Highlight (pink) - Chapter 27 Services: Great and Small > Page 241 · Location 3204

THE FALLACY OF INDEPENDENT DEVELOPMENT AND DEPLOYMENT

Highlight (pink) - Chapter 27 Services: Great and Small > Page 241 · Location 3209

There is some truth to this belief — but only some . First , history has shown that large enterprise systems can be built from monoliths and component - based systems as well as service - based systems . Thus services are not the only option for building scalable systems .

Highlight (yellow) - Chapter 27 Services: Great and Small > Page 241 · Location 3213

coordinated .

Note - Chapter 27 Services: Great and Small > Page 241 · Location 3214

The chapter clarifies that services alone do not constitute an architecture since the true architecture lies in the boundaries adhering to the Dependency Rule. Creating services that obey the Dependency Rule. interface ServiceInterface { void executeService(); } class ConcreteService implements ServiceInterface { public void executeService() { // Service logic } } class ServiceConsumer { private final ServiceInterface service; public ServiceConsumer(ServiceInterface service) { this.service = service; } void performAction() { service.executeService(); } } ---- class DataRepository { static Map<String, String> data = new HashMap<>(); void addData(String key, String value) { data.put(key, value); } String getData(String key) { return data.get(key); } } --- Coordinated deployment due to data dependencies. class DeploymentManager { void deployService(ServiceInterface service) { // Deploy the service, which may need coordination with other services. } }

Highlight (pink) - Chapter 27 Services: Great and Small > Page 246 · Location 3268

Figure 27.3 Each service has its own internal component design , enabling new features to be added as new derivative classes

Highlight (yellow) - Chapter 28 The Test Boundary > Page 250 · Location 3293

things ? What about acceptance tests , functional tests , Cucumber tests , TDD tests , BDD tests , component tests , and so on ?

Note - Chapter 28 The Test Boundary > Page 250 · Location 3294

Acceptance Tests: Acceptance testing, also known as User Acceptance Testing (UAT), involves testing the system to ensure it meets the business requirements. It is usually done from the user's perspective to confirm that the system does what users expect it to do in the real-world scenario. Functional Tests: Functional testing verifies that each function of the software application operates in conformance with the required specification. This includes testing APIs, user interfaces, databases, security, client/server communication, and other functionality of the application. Cucumber Tests: Cucumber is a tool used for running automated acceptance tests written in a Behavior Driven Development (BDD) style. Cucumber tests are written in Gherkin language, which is designed to be non-technical and human-readable, describing software behaviors without detailing how that functionality is implemented. TDD Tests: Test-Driven Development (TDD) is a software development approach where you write a test before you write just enough production code to fulfill that test and refactoring. The tests in TDD are usually unit tests which are intended to specify and validate what the code will do. BDD Tests: Behavior-Driven Development (BDD) extends TDD by writing test cases in natural language that non-programmers can read. BDD focuses on the behavioral aspect of the system rather than the implementation aspect and is often facilitated by frameworks such as Cucumber, JBehave, or SpecFlow. Component Tests: Component testing, also known as module or unit testing, involves testing individual components in isolation from the rest of the system. The purpose is to validate that each unit of the software performs as designed. This is often done in the context of TDD.

Highlight (yellow) - Chapter 28 The Test Boundary > Page 253 · Location 3342

Such tests often wind up on the maintenance room floor — discarded because they are too difficult to maintain .

Note - Chapter 28 The Test Boundary > Page 254 · Location 3343

Idea

Highlight (yellow) - Chapter 29 Clean Embedded Architecture > Page 261 · Location 3455

principles in this book are not applicable to embedded systems .

Highlight (yellow) - Chapter 29 Clean Embedded Architecture > Page 266 · Location 3525

The acmetypes.h header

Note - Chapter 29 Clean Embedded Architecture > Page 266 · Location 3525

this code provides header files with extensions to the C language to access processor features, leading to a non-standard C code that is bound to a specific processor family. This creates a dependency that can later become problematic if the need arises to move the application to a different hardware platform. A cleaner approach would be to confine such device-specific details within firmware and use a Processor Abstraction Layer (PAL), allowing higher-level firmware and the embedded software to be tested off-target, promoting a more flexible and maintainable codebase

Highlight (yellow) - Chapter 29 Clean Embedded Architecture > Page 271 · Location 3578

OSAL ,

Note - Chapter 29 Clean Embedded Architecture > Page 271 · Location 3578

Here's a simple code example to illustrate this principle: // Operating System Abstraction Layer Interface public interface FileSystem { File createFile(String path); void deleteFile(String path); byte[] readFile(String path); void writeFile(String path, byte[] data); } // Implementation for a specific operating system public class WindowsFileSystem implements FileSystem { // Methods that directly use Windows system calls } // Implementation for another operating system public class UnixFileSystem implements FileSystem { // Methods that directly use Unix system calls } // Application code that uses the FileSystem abstraction public class ApplicationService { private final FileSystem fileSystem; public ApplicationService(FileSystem fileSystem) { this.fileSystem = fileSystem; } public void performFileOperations() { // Use the FileSystem interface, not directly OS system calls fileSystem.createFile("example.txt"); fileSystem.writeFile("example.txt", "Hello, world!".getBytes()); // ... } }

PART VI Details

Highlight (pink) - Chapter 34 The Missing Chapter > Page 304 · Location 3909

the implementation details ,

Highlight (pink) - Chapter 34 The Missing Chapter > Page 306 · Location 3948

switch to vertical layering ( “ package by feature ” )

Highlight (pink) - Chapter 34 The Missing Chapter > Page 315 · Location 4038

IMPLEMENTATION DETAILS

Highlight (yellow) - Chapter 34 The Missing Chapter > Page 321 · Location 4120

and watch out for coupling in other areas , such as data models . The devil is in the implementation details .