
Effective Software Design
Description
Book Introduction
Finding solutions to the challenges of software development from a trade-off perspective!
Effective Software Design teaches you how to make better decisions about application design, planning, and implementation.
Analyze real-world scenarios where poor trade-off decisions were made and explain how decisions could have been made differently.
This book presents the pros and cons of various approaches and explores patterns that are always valid in software design.
You'll understand how code duplication affects the coupling and evolution speed of your system, and how seemingly simple requirements can harbor hidden nuances around date and time information.
Let's explore how to efficiently narrow the optimization scope and ensure consistency in a distributed system, following the 80/20 Pareto principle.
By applying the author's hard-won experience to your own projects, you can avoid mistakes and adopt a more prudent approach to decision-making.
Effective Software Design teaches you how to make better decisions about application design, planning, and implementation.
Analyze real-world scenarios where poor trade-off decisions were made and explain how decisions could have been made differently.
This book presents the pros and cons of various approaches and explores patterns that are always valid in software design.
You'll understand how code duplication affects the coupling and evolution speed of your system, and how seemingly simple requirements can harbor hidden nuances around date and time information.
Let's explore how to efficiently narrow the optimization scope and ensure consistency in a distributed system, following the 80/20 Pareto principle.
By applying the author's hard-won experience to your own projects, you can avoid mistakes and adopt a more prudent approach to decision-making.
- You can preview some of the book's contents.
Preview
index
▣ Chapter 1: Introduction
1.1 The consequences of all decisions and patterns
__1.1.1 Unit Test Decisions
__1.1.2 Ratio of unit tests and integration tests
1.2 Code Design Patterns and Why They Don't Always Work
__1.2.1 Measuring the code
1.3 Why Architectural Design Patterns Don't Always Work
__1.3.1 Scalability and Resilience
__1.3.2 Development Speed
__1.3.3 Complexity of Microservices
summation
▣ Chapter 2: Code Duplication vs. Flexibility - Code Duplication Isn't Always Bad
2.1 Common code and duplication between codebases
__2.1.1 Adding a new business requirement that requires code duplication
__2.1.2 Implementing new business requirements
__2.1.3 Results Evaluation
2.2 Sharing code between libraries and codebases
__2.2.1 Evaluating the Tradeoffs and Drawbacks of Shared Libraries
__2.2.2 Creating a shared library
2.3 Extracting code into independent microservices
__2.3.1 Examining the Tradeoffs and Drawbacks of Independent Services
__2.3.2 Conclusions on Independent Services
2.4 Improving Loose Coupling with Code Duplication
2.5 API design using inheritance to reduce duplication
__2.5.1 Extracting the Basic Request Handler
__2.5.2 Examining inheritance and strong coupling
__2.5.3 Examining the Tradeoffs Between Inheritance and Composition
__2.5.4 Examining Inherent and Accidental Duplication
summation
▣ Chapter 3: Exceptions and Error Handling Patterns to Consider in Your Code
3.1 Hierarchy of Exceptions
__3.1.1 Catching all exceptions vs. more granular error handling
3.2 Best practices for handling exceptions in your own code
__3.2.1 Handling Checked Exceptions in Public APIs
__3.2.2 Handling Unchecked Exceptions in Public APIs
3.3 Anti-patterns to watch out for in exception handling
__3.3.1 Closing resources when an error occurs
__3.3.2 Anti-patterns for using exceptions to control application flow
3.4 Exceptions from third-party libraries
3.5 Exceptions to watch out for in multithreaded environments
__3.5.1 Exception Handling in Asynchronous Workflows Using the Promise API
3.6 A functional approach to error handling with Try
__3.6.1 Using Try in Actual Production Service Code
__3.6.2 Mixing Try with code that throws exceptions
3.7 Performance comparison of exception handling code
summation
▣ Chapter 4: Balancing Flexibility and Complexity
4.1 Robust but not scalable API
__4.1.1 New component design
__4.1.2 Starting with the most intuitive code
4.2 Allowing clients to provide their own metrics frameworks
4.3 Providing API extensibility through hooks
__4.3.1 Preventing unexpected use of the hook API
__4.3.2 Performance Impact of the Hook API
4.4 Providing API extensibility through listeners
__4.4.1 Using Listeners vs. Hooks
__4.4.2 Design Invariance
4.5 Flexible Analysis vs. Maintenance Costs of APIs
summation
▣ Chapter 5: Premature Optimization vs. Optimizing Hot Code Paths - Decisions That Affect Code Performance
5.1 When Premature Optimization Is Bad
__5.1.1 Creating an Account Processing Pipeline
__5.1.2 Processing Optimization Based on Incorrect Assumptions
__5.1.3 Performance Optimization Benchmarks
5.2 Hot Code Paths
__5.2.1 Understanding the Pareto Principle in the Context of Software Systems
__5.2.2 Configuring the number of concurrent users (threads) for a given SLA
5.3 Word services with potential hot code paths
__5.3.1 Get the word of the day
__5.3.2 Verifying if a word exists
__5.3.3 Exposing WordsService to the Public Using an HTTP Service
5.4 Hot Code Path Detection
__5.4.1 Creating API Performance Tests Using Gatling
__5.4.2 Measuring Code Paths Using MetricRegistry
5.5 Performance improvements for hot code paths
__5.5.1 Generating JMH Microbenchmarks for Existing Solutions
__5.5.2 Optimizing word-exists using a cache
__5.5.3 Changed performance test to accept more input words
summation
▣ Chapter 6: Cost vs. Simplicity of API Maintenance
6.1 Basic Libraries Used by Other Tools
__6.1.1 Creating a Cloud Service Client
__6.1.2 Exploring Authentication Strategies
__6.1.3 Understanding the Configuration Mechanism
6.2 Exposing the settings of dependent libraries directly to the outside world
__6.2.1 Configuring the Deployment Tool
6.3 Tools to abstract the configuration of dependency libraries
__6.3.1 Configuring streaming tools
6.4 Adding new settings for the cloud client library
__6.4.1 Adding a new setting to the deployment tool
__6.4.2 Adding new settings to the streaming tool
__6.4.3 Comparing the two solutions in terms of UX friendliness and maintainability
6.5 Deciding to deprecate or remove a setting from the Cloud Client Library
__6.5.1 Removing Settings from the Deployment Tool
__6.5.2 Removing settings from streaming tools
__6.5.3 Comparing the two solutions in terms of UX friendliness and maintainability
summation
▣ Chapter 7: Working Efficiently with Date and Time Data
7.1 Concepts of date and time information
__7.1.1 Computer Time: Instants, Epochs, and Time Intervals
__7.1.2 Commercial Time: Calendar System, Date, Time, Date Interval
__7.1.3 Time Zones, UTC, and UTC Offset
__7.1.4 Head-scratching date and time concepts
7.2 Preparing to work with date and time information
__7.2.1 Limiting the scope
__7.2.2 Make date and time requirements clear
__7.2.3 Using the correct library or package
7.3 Implementing Date and Time Codes
__7.3.1 Applying the Concept Consistently
__7.3.2 Improving testability by avoiding defaults
__7.3.3 Representing Date and Time Values in Text
__7.3.4 Explaining code with comments
7.4 Special cases that must be specified and tested
__7.4.1 Calendar Arithmetic Operations
__7.4.2 Time zone conversion at midnight
__7.4.3 Handling Ambiguous or Skipped Times
__7.4.4 Working with Evolving Timeline Data
summation
▣ Chapter 8: Leveraging Data Locality and Memory on Computers
8.1 What is data locality?
__8.1.1 Moving calculations to data
__8.1.2 Scaling processing scale using data locality
8.2 Data Partitioning and Data Dividing
__8.2.1 Offline Big Data Partitioning
__8.2.2 Partitioning vs. Sharding
__8.2.3 Partitioning Algorithm
8.3 Joining Big Data Sets from Multiple Partitions
__8.3.1 Joining data within the same physical computer
__8.3.2 Join operations that require data movement
__8.3.3 Join Optimization Using Broadcasting
8.4 Data Processing: Memory vs. Disk
__8.4.1 Disk-based processing
__8.4.2 Why MapReduce is Needed
__8.4.3 Calculating Access Time
__8.4.4 RAM-based processing
8.5 Implementing Joins Using Apache Spark
__8.5.1 Implementing Joins Without Broadcast
__8.5.2 Implementing Joins with Broadcast
summation
▣ Chapter 9: External Libraries - The libraries you use will soon become your code.
9.1 Importing Libraries and Taking Full Responsibility for Configuration: Beware of Defaults
9.2 Concurrency Model and Scalability
__9.2.1 Using Asynchronous and Synchronous APIs
__9.2.2 Distributed Scalability
9.3 Testability
__9.3.1 Test Library
__9.3.2 Testing with fake objects (test doubles) and mock objects
__9.3.3 Test Toolkit Integration
9.4 Dependencies on Third-Party Libraries
Avoiding version conflicts with __9.4.1
__9.4.2 Too many dependencies
9.5 Selecting and Maintaining External Dependencies
__9.5.1 First impression
__9.5.2 Different Approaches to Code Reuse
__9.5.3 Vendor Dependency
__9.5.4 License
__9.5.5 Libraries vs. Frameworks
__9.5.6 Security and Updates
__9.5.7 Checklist for Decision Making
summation
▣ Chapter 10: Consistency and Atomicity in Distributed Systems
10.1 Send data source at least once
__10.1.1 Traffic between single-node services
__10.1.2 Retrying application calls
__10.1.3 Data Creation and Idempotency
__10.1.4 Understanding CQRS (Command Query Responsibility Segregation)
10.2 Simple implementation of the deduplication library
10.3 Common Mistakes When Implementing Deduplication in Distributed Systems
__10.3.1 Context with only one node
__10.3.2 Multi-node context
10.4 Make logic atomic to avoid race conditions
summation
▣ Chapter 11: Delivery Semantics in Distributed Systems
11.1 Architecture of Event-Driven Applications
11.2 Producer and Consumer Applications Based on Apache Kafka
__11.2.1 A Look at Kafka's Consumer Side
__11.2.2 Understanding Kafka Broker Settings
11.3 Producer Logic
__11.3.1 Choosing Consistency vs. Availability for Producers
11.4 Consumer Codes and Various Delivery Semantics
__11.4.1 Manually committing consumers
__11.4.2 Restart from the earliest or most recent offset
__11.4.3 (virtually) exactly once semantics
11.5 Leveraging Shipping Semantics to Provide Fault Tolerance
summation
▣ Chapter 12: Managing Versions and Compatibility
12.1 Version Control in the Abstract
__Properties of version 12.1.1
__12.1.2 Backward and Forward Compatibility
__12.1.3 Semantic Versioning
__12.1.4 Marketing Version
12.2 Versioning for Libraries
__12.2.1 Source Code, Binaries, and Semantic Compatibility
__12.2.2 Dependency Graph and Diamond Dependency
__12.2.3 Techniques for Handling Compatibility-Breaking Changes
__12.2.4 Managing internal-only libraries
12.3 Versioning for Network APIs
__12.3.1 Context of a network API call
__12.3.2 Customer-Friendly Clarity
__12.3.3 General versioning strategy
__12.3.4 Additional Versioning Considerations
12.4 Versioning for Data Repositories
__12.4.1 A Brief Introduction to Protocol Buffers
__12.4.2 What changes break compatibility?
__12.4.3 Migrating data within a repository
__12.4.4 Anticipating the Unexpected
__12.4.5 Separating API and Repository Representation
__12.4.6 Evaluating the Repository Format
summation
▣ Chapter 13: Following the Latest Trends vs. Reducing Code Maintenance Costs
13.1 When to Use a Dependency Injection Framework?
__13.1.1 DIY (Do-it-yourself) Dependency Injection
__13.1.2 Using a Dependency Injection Framework
13.2 When using reactive programming
__13.2.1 Creating a single-threaded, blocking process
__13.2.2 Using CompletableFuture
__13.2.3 Implementing a Reactive Solution
13.3 When using functional programming
__13.3.1 Generating Functional Code from Non-Functional Languages
__13.3.2 Tail Recursion Optimization
__13.3.3 Leveraging Immutability
13.4 Lazy vs. Eager Evaluation
summation
1.1 The consequences of all decisions and patterns
__1.1.1 Unit Test Decisions
__1.1.2 Ratio of unit tests and integration tests
1.2 Code Design Patterns and Why They Don't Always Work
__1.2.1 Measuring the code
1.3 Why Architectural Design Patterns Don't Always Work
__1.3.1 Scalability and Resilience
__1.3.2 Development Speed
__1.3.3 Complexity of Microservices
summation
▣ Chapter 2: Code Duplication vs. Flexibility - Code Duplication Isn't Always Bad
2.1 Common code and duplication between codebases
__2.1.1 Adding a new business requirement that requires code duplication
__2.1.2 Implementing new business requirements
__2.1.3 Results Evaluation
2.2 Sharing code between libraries and codebases
__2.2.1 Evaluating the Tradeoffs and Drawbacks of Shared Libraries
__2.2.2 Creating a shared library
2.3 Extracting code into independent microservices
__2.3.1 Examining the Tradeoffs and Drawbacks of Independent Services
__2.3.2 Conclusions on Independent Services
2.4 Improving Loose Coupling with Code Duplication
2.5 API design using inheritance to reduce duplication
__2.5.1 Extracting the Basic Request Handler
__2.5.2 Examining inheritance and strong coupling
__2.5.3 Examining the Tradeoffs Between Inheritance and Composition
__2.5.4 Examining Inherent and Accidental Duplication
summation
▣ Chapter 3: Exceptions and Error Handling Patterns to Consider in Your Code
3.1 Hierarchy of Exceptions
__3.1.1 Catching all exceptions vs. more granular error handling
3.2 Best practices for handling exceptions in your own code
__3.2.1 Handling Checked Exceptions in Public APIs
__3.2.2 Handling Unchecked Exceptions in Public APIs
3.3 Anti-patterns to watch out for in exception handling
__3.3.1 Closing resources when an error occurs
__3.3.2 Anti-patterns for using exceptions to control application flow
3.4 Exceptions from third-party libraries
3.5 Exceptions to watch out for in multithreaded environments
__3.5.1 Exception Handling in Asynchronous Workflows Using the Promise API
3.6 A functional approach to error handling with Try
__3.6.1 Using Try in Actual Production Service Code
__3.6.2 Mixing Try with code that throws exceptions
3.7 Performance comparison of exception handling code
summation
▣ Chapter 4: Balancing Flexibility and Complexity
4.1 Robust but not scalable API
__4.1.1 New component design
__4.1.2 Starting with the most intuitive code
4.2 Allowing clients to provide their own metrics frameworks
4.3 Providing API extensibility through hooks
__4.3.1 Preventing unexpected use of the hook API
__4.3.2 Performance Impact of the Hook API
4.4 Providing API extensibility through listeners
__4.4.1 Using Listeners vs. Hooks
__4.4.2 Design Invariance
4.5 Flexible Analysis vs. Maintenance Costs of APIs
summation
▣ Chapter 5: Premature Optimization vs. Optimizing Hot Code Paths - Decisions That Affect Code Performance
5.1 When Premature Optimization Is Bad
__5.1.1 Creating an Account Processing Pipeline
__5.1.2 Processing Optimization Based on Incorrect Assumptions
__5.1.3 Performance Optimization Benchmarks
5.2 Hot Code Paths
__5.2.1 Understanding the Pareto Principle in the Context of Software Systems
__5.2.2 Configuring the number of concurrent users (threads) for a given SLA
5.3 Word services with potential hot code paths
__5.3.1 Get the word of the day
__5.3.2 Verifying if a word exists
__5.3.3 Exposing WordsService to the Public Using an HTTP Service
5.4 Hot Code Path Detection
__5.4.1 Creating API Performance Tests Using Gatling
__5.4.2 Measuring Code Paths Using MetricRegistry
5.5 Performance improvements for hot code paths
__5.5.1 Generating JMH Microbenchmarks for Existing Solutions
__5.5.2 Optimizing word-exists using a cache
__5.5.3 Changed performance test to accept more input words
summation
▣ Chapter 6: Cost vs. Simplicity of API Maintenance
6.1 Basic Libraries Used by Other Tools
__6.1.1 Creating a Cloud Service Client
__6.1.2 Exploring Authentication Strategies
__6.1.3 Understanding the Configuration Mechanism
6.2 Exposing the settings of dependent libraries directly to the outside world
__6.2.1 Configuring the Deployment Tool
6.3 Tools to abstract the configuration of dependency libraries
__6.3.1 Configuring streaming tools
6.4 Adding new settings for the cloud client library
__6.4.1 Adding a new setting to the deployment tool
__6.4.2 Adding new settings to the streaming tool
__6.4.3 Comparing the two solutions in terms of UX friendliness and maintainability
6.5 Deciding to deprecate or remove a setting from the Cloud Client Library
__6.5.1 Removing Settings from the Deployment Tool
__6.5.2 Removing settings from streaming tools
__6.5.3 Comparing the two solutions in terms of UX friendliness and maintainability
summation
▣ Chapter 7: Working Efficiently with Date and Time Data
7.1 Concepts of date and time information
__7.1.1 Computer Time: Instants, Epochs, and Time Intervals
__7.1.2 Commercial Time: Calendar System, Date, Time, Date Interval
__7.1.3 Time Zones, UTC, and UTC Offset
__7.1.4 Head-scratching date and time concepts
7.2 Preparing to work with date and time information
__7.2.1 Limiting the scope
__7.2.2 Make date and time requirements clear
__7.2.3 Using the correct library or package
7.3 Implementing Date and Time Codes
__7.3.1 Applying the Concept Consistently
__7.3.2 Improving testability by avoiding defaults
__7.3.3 Representing Date and Time Values in Text
__7.3.4 Explaining code with comments
7.4 Special cases that must be specified and tested
__7.4.1 Calendar Arithmetic Operations
__7.4.2 Time zone conversion at midnight
__7.4.3 Handling Ambiguous or Skipped Times
__7.4.4 Working with Evolving Timeline Data
summation
▣ Chapter 8: Leveraging Data Locality and Memory on Computers
8.1 What is data locality?
__8.1.1 Moving calculations to data
__8.1.2 Scaling processing scale using data locality
8.2 Data Partitioning and Data Dividing
__8.2.1 Offline Big Data Partitioning
__8.2.2 Partitioning vs. Sharding
__8.2.3 Partitioning Algorithm
8.3 Joining Big Data Sets from Multiple Partitions
__8.3.1 Joining data within the same physical computer
__8.3.2 Join operations that require data movement
__8.3.3 Join Optimization Using Broadcasting
8.4 Data Processing: Memory vs. Disk
__8.4.1 Disk-based processing
__8.4.2 Why MapReduce is Needed
__8.4.3 Calculating Access Time
__8.4.4 RAM-based processing
8.5 Implementing Joins Using Apache Spark
__8.5.1 Implementing Joins Without Broadcast
__8.5.2 Implementing Joins with Broadcast
summation
▣ Chapter 9: External Libraries - The libraries you use will soon become your code.
9.1 Importing Libraries and Taking Full Responsibility for Configuration: Beware of Defaults
9.2 Concurrency Model and Scalability
__9.2.1 Using Asynchronous and Synchronous APIs
__9.2.2 Distributed Scalability
9.3 Testability
__9.3.1 Test Library
__9.3.2 Testing with fake objects (test doubles) and mock objects
__9.3.3 Test Toolkit Integration
9.4 Dependencies on Third-Party Libraries
Avoiding version conflicts with __9.4.1
__9.4.2 Too many dependencies
9.5 Selecting and Maintaining External Dependencies
__9.5.1 First impression
__9.5.2 Different Approaches to Code Reuse
__9.5.3 Vendor Dependency
__9.5.4 License
__9.5.5 Libraries vs. Frameworks
__9.5.6 Security and Updates
__9.5.7 Checklist for Decision Making
summation
▣ Chapter 10: Consistency and Atomicity in Distributed Systems
10.1 Send data source at least once
__10.1.1 Traffic between single-node services
__10.1.2 Retrying application calls
__10.1.3 Data Creation and Idempotency
__10.1.4 Understanding CQRS (Command Query Responsibility Segregation)
10.2 Simple implementation of the deduplication library
10.3 Common Mistakes When Implementing Deduplication in Distributed Systems
__10.3.1 Context with only one node
__10.3.2 Multi-node context
10.4 Make logic atomic to avoid race conditions
summation
▣ Chapter 11: Delivery Semantics in Distributed Systems
11.1 Architecture of Event-Driven Applications
11.2 Producer and Consumer Applications Based on Apache Kafka
__11.2.1 A Look at Kafka's Consumer Side
__11.2.2 Understanding Kafka Broker Settings
11.3 Producer Logic
__11.3.1 Choosing Consistency vs. Availability for Producers
11.4 Consumer Codes and Various Delivery Semantics
__11.4.1 Manually committing consumers
__11.4.2 Restart from the earliest or most recent offset
__11.4.3 (virtually) exactly once semantics
11.5 Leveraging Shipping Semantics to Provide Fault Tolerance
summation
▣ Chapter 12: Managing Versions and Compatibility
12.1 Version Control in the Abstract
__Properties of version 12.1.1
__12.1.2 Backward and Forward Compatibility
__12.1.3 Semantic Versioning
__12.1.4 Marketing Version
12.2 Versioning for Libraries
__12.2.1 Source Code, Binaries, and Semantic Compatibility
__12.2.2 Dependency Graph and Diamond Dependency
__12.2.3 Techniques for Handling Compatibility-Breaking Changes
__12.2.4 Managing internal-only libraries
12.3 Versioning for Network APIs
__12.3.1 Context of a network API call
__12.3.2 Customer-Friendly Clarity
__12.3.3 General versioning strategy
__12.3.4 Additional Versioning Considerations
12.4 Versioning for Data Repositories
__12.4.1 A Brief Introduction to Protocol Buffers
__12.4.2 What changes break compatibility?
__12.4.3 Migrating data within a repository
__12.4.4 Anticipating the Unexpected
__12.4.5 Separating API and Repository Representation
__12.4.6 Evaluating the Repository Format
summation
▣ Chapter 13: Following the Latest Trends vs. Reducing Code Maintenance Costs
13.1 When to Use a Dependency Injection Framework?
__13.1.1 DIY (Do-it-yourself) Dependency Injection
__13.1.2 Using a Dependency Injection Framework
13.2 When using reactive programming
__13.2.1 Creating a single-threaded, blocking process
__13.2.2 Using CompletableFuture
__13.2.3 Implementing a Reactive Solution
13.3 When using functional programming
__13.3.1 Generating Functional Code from Non-Functional Languages
__13.3.2 Tail Recursion Optimization
__13.3.3 Leveraging Immutability
13.4 Lazy vs. Eager Evaluation
summation
Detailed image

Publisher's Review
★ What this book covers ★
◎ Reason about the system to make intuitive and better design decisions
◎ Understand how to balance trade-offs and the trends of trade-offs
◎ Choosing the right library for the problem
◎ Thoroughly analyze the entire dependency of the service
◎ Understand transmission semantics and their impact on distributed architectures.
◎ Design and perform performance tests to detect hot code paths and verify system SLAs.
◎ Detect and optimize hot code paths to focus optimization on the root cause.
◎ Determine the appropriate data model for date/time processing to avoid common (but subtle) mistakes.
◎ Infer compatibility and version control to prevent unexpected problems with API clients.
◎ Understand the concepts of tight coupling and loose coupling and how these concepts affect coordination of work between teams.
◎ Make requirements clear until they are accurate, easy to implement, and easy to test.
◎ Optimizing APIs for a friendly user experience
◎ Reason about the system to make intuitive and better design decisions
◎ Understand how to balance trade-offs and the trends of trade-offs
◎ Choosing the right library for the problem
◎ Thoroughly analyze the entire dependency of the service
◎ Understand transmission semantics and their impact on distributed architectures.
◎ Design and perform performance tests to detect hot code paths and verify system SLAs.
◎ Detect and optimize hot code paths to focus optimization on the root cause.
◎ Determine the appropriate data model for date/time processing to avoid common (but subtle) mistakes.
◎ Infer compatibility and version control to prevent unexpected problems with API clients.
◎ Understand the concepts of tight coupling and loose coupling and how these concepts affect coordination of work between teams.
◎ Make requirements clear until they are accurate, easy to implement, and easy to test.
◎ Optimizing APIs for a friendly user experience
GOODS SPECIFICS
- Date of issue: March 12, 2025
- Page count, weight, size: 472 pages | 188*240*19mm
- ISBN13: 9791158395643
You may also like
카테고리
korean
korean