-
Notifications
You must be signed in to change notification settings - Fork 9
Architecture
- Software Architecture
- Architectural Drivers
- API Design
- Abstraction
- Dependency Injection
- Common Architectural Approaches
- Clean Architecture
- Hexagonal Architecture
- Deployment
- Big Ball of Mud
- Functional Architecture
- Role of the Software Architect
- System Thinking
- References
The only way to go fast, is to go well. (Robert Martin)
Architecture is what happens, not what is planned. [e]
Architecture consists of
- Structure - components, relations, styles used
- Functional requirements
-
Quality requirements (characteristics) -ilities the system must support:
- Availability
- Reliability
- Testability
- Scalability
- Security
- Maintainability
- Agility
- Extensibility
- Configurability
- Fault tolerance
- Elasticity
- Recoverability
- Performance
- Deployability
- Portability
- Compatibility
- Learnability
- Localization
- Usability
- Accessibility
- Archivability
- Privacy
- Functional suitability, completeness, correctness and appropriateness
- Decisions - rules for how the system should be constructed.
- Design principles - guidelines for constructing the system rather than rules.
-
Constraints
- Physical requirements, system distribution, technologies used, environment.
- Process, support, regulatory constraints.
- Timelines, budget, human resources.
Software Architecture consists from structure (building blocks), relations (organizations) and concepts (decisions, principles).
Architecture isn't good or bad, it's fit or unfit for a purpose. [c]
Good architecture makes the system easy to understand, easy to develop, easy to maintain, and easy to deploy. [a]
"Simple things should be simple, complex things should be possible." ~ Alan Key
Enterprise architecture is the organizing logic for business processes and IT infrastructure. [c]
Simplicity is often not an easy goal to achieve. Programmers love complexity, so they have a strong tendency to overcomplicate their work. It's often easier to quickly build an overly complex system than it is to spend the time required to make the system simple. [d]
- Architecture evolves with the time: how it is (documentation) --> how it should be (plan).
- Architecture (design and implementation) is an iterative process.
- Every decision is always made in a context.
- Different problems have different solutions.
- A "good" decision in one context may be "bad" in another. Every choice has a good and bad side and is made in the context of overall criteria that are defined by necessity. Decisions aren't binary. You often have shades of goodness - consequences associated with your decision - that can mean that none of the possibilities you're contemplating is "best". [d]
- You can only achieve a long-lasting architecture if you constantly keep an eye on technical debt.
- Design APIs so, that they can stay untouched forever.
- Prefer to introduce a new endpoint over changing (and breaking) an existing one.
- Be conservative in what you do, be liberal in what you accept from others. (Postel's Law)
- Makes the system more robust, but less precise and more complex (breaks the separation of concerns).
- Use consumer-driven contracts.
- For legacy systems without unit tests first, write acceptance tests and then start refactoring with unit tests.
- Strangler pattern: capture and intercept calls to the old system and decide to route either to the legacy or new code.
- Partial boundary is to do all the work necessary to create independently compilable and deployable components, and then simply keep them together in the same component.
- Passing database rows and tables around the system as objects couples the use-cases, business rules, and even the UI to the relational structure of the data.
- Performance is an architectural concern that can be entirely encapsulated and separated from the business rules.
- There is no such thing as an object-relational mapper (ORM). The reason is simple: Objects are not data structures. At least, they are not data structures from their users' point of view. The users of an object cannot see the data, since it is all private. Those users see only the public methods of that object. So, from the user's point of view, an object is simply a set of operations.
- The devil is the implementation details, and it's really easy to fall at the last hurdle if you don't give that some thought, too.
- When facing two or more options that are delivering roughly the same value, choose the one that makes future changes easier.
- Think about up-front design as being about creating a starting point and setting a direction for the team, rather than creating a plan that needs to be followed.
- The more experience you have in the application domain and with software development, in general, the more likely you are to make good predictions about future changes. That's why it's typically difficult to get the design right immediately when starting a project.
- Software architecture is about putting some boundaries in place, inside which you can build software using whatever xDD and agile practices you like.
- New technologies with old practices will result in anti-patterns.
-
Agile architecture:
- Architecture is an ongoing activity.
- Make changes reversible (eg. DB migrations).
- Continuously check whether the architecture works as expected.
- In times of high uncertainty, as we are facing them today both in business and technology, the value of architecture options also increases. Business should therefore invest more into architecture.
- Architecture options are rarely free. You may pay with increased complexity or loss of another option. [c]
- Architecture is a profession of trade-offs: flexibility brings complexity; decoupling increases latency; distributing components introduces communication overhead, etc.
- Design patterns are typically implementation details that are applied by OO programmers when they translate the initial design to code. [d]
Component is a reasonably large-scale code structure within an application, with a well-defined API, that could potentially be swapped out for another implementation.
Module is a unit of deployment.
Service is an organization of ownership; can be built, deployed, and run independently; autonomous owns all its data, owns its communication contact.
Layer is an organization of code; interface -> logic -> resources.
Tier is a runtime organization (e.g. gateway tier, backend tier, storage tier).
"All models are wrong, but some are useful." ~ George Box
Architecture Diagrams are models of reality. [c]
- Instead of trying to make models right, you should think about whether your models are useful.
Completeness shouldn't be your architecture diagrams' primary goal. Rather, you should depict the appropriate scope. [c]
- Big enough to be meaningful, small enough to be comprehensible, and cohesive enough to make sense.
If it's your decision, it's design; if not, it's a requirement. ~ Alistair Cockburn
- Each of our decisions is made in a given context.
- The same decision made in one context can bring great results, while in another it can cause devastating failure.
Architectural drivers are formally defined as the set of requirements that have significant influence over your architecture.
- Functional Requirements – what and how problems does the system solve
- Quality Attributes – a set of attributes that determine the quality of architecture like maintainability or scalability.
- Technical Constraints – technology standards, tools limitations, team experience
- Business Constraints – budget, hard deadline
All Architectural Drivers are connected to each other and often focus on one causes loss of another.
- Software architecture is a continuous choice between one driver and another.
If you run a kitchen and you only ever cook food, because selling cooked food is your business - if you never clean the dishes, never scrape the grill, never organize the freezer, etc - the health inspector will shut your shit down pretty quickly.
Software, on the other hand, doesn't have health inspectors. It has kitchen staff who become more alarmed over time at the state of the kitchen they're working in every day, and if nothing is done about it, there will come a point where the kitchen starts failing to produce edible meals.
Generally, you can either convince decision makers that cleaning the kitchen is more profitable in the long run or you can dust off your resume and get out before it burns down.
- API should take the form of use-cases.
- When in doubt leave it out - You can always add, but you can never remove.
- Implementation should not impact API.
- Don't let implementation details “leak” into API.
There's a rule of thumb to determine whether a class leaks its implementation details: If the number of operations the client has to invoke to achieve a single goal is greater than one. Ideally, any individual goal should be achieved with a single operation.
Removing things is often more difficult than building them, to begin with because existing users are often using the system beyond its original design.
Evolving a system in place is usually cheaper than replacing it with a new one.
Don't build elaborate APIs that mimic the back-end system's design. Instead, build consumer-driven APIs that provide the service in a format that the front-ends prefer. [b]
- Design interfaces such that they reflect where we are heading, not where we came from.
All problems in computer science can be solved by another level of indirection, except for the problem of too many layers of indirection. (David Wheeler)
Abstraction must solve a problem, otherwise stay concrete.
Stable purpose -> good abstraction -> cohesive implementations.
- Purpose must be separated from requirements.
- The more potentially-useful implementations an interface might have, the better is the interface.
- Interface reflects the purpose.
- Purpose is stable.
- Purpose has no details.
- Purpose is client-oriented.
- Stable purpose is a base for a good abstraction.
- Abstraction is stable and reusable.
- Implementations fulfill the requirements.
- Requirements change and grow.
- Focused, cohesive.
Coupling at the endpoints (Application main, REST controllers) is fine because nothing usually depends on them.
An excessive number of layers of indirection negatively affects the ability to reason about the code. Having as few layers of indirection as possible is necessary. In most backend systems only three are enough:
- Domain model
- Application service layer (controllers)
- Infrastructure
Interfaces with a single implementation are not abstractions and don't provide loose coupling any more than concrete classes. Trying to anticipate future implementations violates the YAGNI principle.
- Use such interfaces only for unmanaged dependencies,
- use concrete classes for managed dependencies.
With an interface, you can remove a circular dependency at compile time, but at runtime. The cognitive load required to understand the code doesn't become any smaller.
Introducing an interface doesn't solve cyclically coupled classes, it only camouflages the problem. The compilation static dependencies are now moved to the runtime.
Solving cycles is only possible on the basis of criteria that grow out of the system's architecture and its domain. If a system has cycles, it is because the task of the classes involved is not clearly defined.
Abstract classes vs. interfaces - Abstract classes can easily be abused as base classes. Base classes can easily turn into ever-changing, ever-growing God Objects.
The best abstractions are those that solve and encapsulate the difficult part of the problem while leaving the user with sufficient flexibility. [c]
Dependency Injection (DI) is a set of software design principles and patterns that enables you to develop loosely coupled code.
- Collaborating classes should rely on infrastructure to provide necessary services.
Inversion of Control (IoC) originally meant any sort of programming style where an overall framework or runtime controlled the program flow.
- IoC is a much broader term that includes, but isn't limited to, DI.
Dependency Inversion Principle prescribes what we would like to accomplish, and DI states how we would like to accomplish it.
- The principle doesn't describe how a consumer should get ahold of its dependencies.
Constructors shouldn't do anything else but injecting dependencies. Keep the constructor free of any other logic to prevent it from any work on dependencies. The Single Responsibility Principle implies that members should do only one thing - the constructor injects dependencies and is free of other concerns.
Only the Application should rely on configuration files. Other parts, such as factories, shouldn't request values from a configuration file but, instead, should be configurable by their callers.
All the following approaches are syntactically identical - Java packages become irrelevant if all of the types are marked as public. So it's important not to use the public
keyword as default and make public only those classes which are a part of the API - the devil is the implementation details.
- Choose the domain-oriented division of a system as the most important criterion for the packet or artifact structure within the system.
- Disassemble domain-oriented units that belong together.
- One public interface and one implementation project cover everything.
Organize code based upon what the code does from a technical perspective. Horizontal view.
- Changes to a layered architecture usually results in changes across all layers.
- Doesn't screams anything about the business domain.
- As the software grows in scale and complexity three large buckets of code aren't sufficient.
- In a layered architecture it easily happends that domain logic is scattered through the layers.
- Often leads to very broad services that serve multiple use-cases.
- Better to have highly-specialized narrow domain services that each serve a single use-case.
- Eg. instead of searching for the user registration use-case in the
UserService
, we'd just open up theRegisterUserService
etc.
- Eg. instead of searching for the user registration use-case in the
Organize code based upon what the code does from a functional perspective. Vertical view.
- Top-level organization screams something about the business domain.
- Higher cohesion, lower coupling, easier to find related code (all sitting in a single package rather than being spread out).
- Problematic when a feature needs the functionality of another (Customer needs Order etc.).
Variations are Hexagonal, Clean, or Onion architectures.
Keep domain (business) related code separate from technical details.
- Because business and technical requirements have different life-cycles.
- The "inside" is technology agnostic and is often described in terms of a ubiquitous language.
- The "outside" is technology specific (database, frameworks, third-parties, ...).
- The "outside" depends on the "inside" - never the other way around.
- May end up creating a lot of adapting and abstracting code, which is not really necessary.
- It's still possible, but mostly undesirable, to bypass the business logic layer to skip around adjacent neighbors (like calling the repository directly from the controller) - _relaxed layered architecture.
Designing with the assumption that we will always add Ports & Adapters at these points of communication between modules and services is a much stronger default stance than not. Even if the “adapter” is a placeholder for the future, having that placeholder in place gives us the opportunity, should the nature of the API change in any way to cope with those changes without having to rewrite all of our code. [f]
Clean Architecture comes at a cost. Since the domain layer is completely decoupled from the outer layers like persistence and UI, we have to maintain a model of our application's entities in each of the layers.
- We have to translate between both representations when the domain layer sends and receives data to and from the other outer layer (eg. persistence).
Renaming eg. AccountService
to SendMoneyService
to narrow its responsibilities means we can now see the use-case "Send Money" just by looking at the class names -> Screaming Architecture screams its intention.
Organize code based by bundling together everything related to a component.
- Hybrid approach to the package by layer and package by feature.
- Component here is a grouping of related functionality behind an interface, which resides inside an execution environment like an application. System > Container (eg. webapp) > Component (eg. service) > Code element (eg. class).
- Separates visual things (UI, web, ...) from the non-visible functionality.
- Separate interface from implementation.
- Don't use
public
when not necessary to avoid creating of the big ball of mud.
The code structure should reflect the architectural intent.
- A service must get what it needs; for example, image processing must get the image as a file or inputstream, not only an id to get the file via a service in a higher layer -> violate clean architecture layers.
- A workaround as a decoupled component, to be removed easily in one step.
- Application must not be aware of the logging implementation, should talk only to an abstraction (like
commons-logging
). - Software architects are the best programmers, and they continue to take on programming tasks.
- Source code dependencies must point only inward, toward higher-level policies. The name of something declared in an outer circle must not be mentioned by the code in an inner circle.
- Definition of the data structure is on the calling side of the boundary. Data passed across a boundary is always in the form most convenient for the inner circle.
- Entity is pure business and nothing else.
- Use-cases depend on Entities; Entities do not depend on Use-cases.
- Use-case describes application-specific business rules as opposed to the Critical Business Rules within the Entities.
- Use-case does not describe the UI other than to informally specify the data coming in from and coming out through an interface.
- Input/Output is irrelevant. Consider UI as a plugin.
- Web is a delivery mechanism - an I/O device - and the architecture should treat it as such. The fact that the application is delivered over the web is a detail and should not dominate the system structure.
- Application screams about the use-cases of the application.
- Architectures are not (or should not be) about frameworks.
- In the Main component the dependencies should be injected by a dependency injection framework. Then, Main should distribute those dependencies normally, without using the framework.
- You can use a framework - just don't couple to it.
- To keep the rest of the application clean, we need an outside component that takes care of the wiring.
- This component has to know all the moving parts to assemble them into a working application (eg. Spring's
@Configuration
).
- This component has to know all the moving parts to assemble them into a working application (eg. Spring's
Gather together those things that change at the same time and for the same reasons. Separate those things that change at different times and or for different reasons.
- System structure is influenced by the social structure of the organization.
- A module should be responsible to one, and only one, actor.
- Only one reason for a change.
- An actor refers to a group of people (one or more) who require the change.
Cohesion is the force that binds together the code responsible to a single actor.
- Separate code that supports different actors.
- Classes and modules that are formed into a component must belong to a cohesive group.
- Classes and modules that are grouped together into a component should be released together.
This principle is about people. Changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function.
The SRP tells us where to draw our boundaries.
The SRP doesn't mean that every module should do just one thing.
- Common Closure Principle (CCP): A component should not have multiple reasons to change.
- A software artifact should be open for extension but closed for modification.
- Adding new code, rather than changing existing code.
- If component A should be protected from changes in component B, then component B should depend on component A.
- Behavior of a software artifact ought to be extendible, without having to modify that artifact.
- Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
- There must be a contract obeyed by the involved parts.
- Services of a derived class should require no more and promise no less than the corresponding services of a base class.
-
No client should be forced to depend on methods it does not use.
-
Common Reuse Principe (CRP): Don't force users of a component to depend on things they don't need.
Any time a consumer depends on an abstraction where some of its members are unused, the ISP is violated.
Dynamically typed languages create systems that are more flexible and less tightly coupled than statically typed languages.
- Because they don't have any interface declarations, all is inferred at runtime.
It is harmful to depend on modules that contain more than you need.
Stop saying "what is this thing?" but rather "what can you do with it?" That leads us toward segregated interfaces. ref
- Code (component) should depend only on interfaces or abstract classes.
- The most flexible systems are those in which source code dependencies refer only to abstractions, not to concretions.
- High-level policies should not depend on the code that implements low-level details. Rather, details should depend on policies.
- Don't refer to, derive from volatile concrete classes.
- Don't override concrete functions.
- Never mention the name of anything concrete and volatile.
Nothing concretes should be depended on.
- Treating this idea as a rule is unrealistic, because software systems must depend on many concrete facilities (eg.
String
class in Java). - Ignore the stable background of the operating system and platform facilities.
- Those concrete dependencies are very stable.
- It is the volatile concrete elements of our system that we want to avoid depending on.
- Interfaces are less volatile as a change in a concrete implementation doesn't correspond to a change in the interface (but the opposite is always true).
interface Foo
is a true abstraction. You can depend on abstraction in two ways:
-
- By either subtyping it (
SomeFoo
depends onFoo
, becauseSomeFoo implements Foo
)
- By either subtyping it (
-
- Or by referencing it (by declaring a field, an argument, return value, or referring/mentioning to the
Foo
in any other way (straight or indirect), you make the class dependent onFoo
)
- Or by referencing it (by declaring a field, an argument, return value, or referring/mentioning to the
- DIP just says that
Foo
should never depend neither onSomeFoo
, nor on anything that usesFoo
.
- Classes and modules that are grouped together into a component should be releasable together.
- Stable component should also be abstract so that its stability does not prevent it from being extended.
- Unstable component should be concrete since it its instability allows the concrete code within it to be easily changed.
Abstract components are very stable and, therefore, are ideal targets for less stable components to depend on.
- Dependencies run in the direction of abstraction.
- Every piece of functionality the application offers is available through an API.
- The application can be deployed in headless mode, so only the API is available, and other programs can make use of its functionality.
- The application communicates over ports to external agencies.
- For each external device there is an adapter that converts the API definition to the signals needed by that device and vice versa (GUI is an example of an adapter).
- There will typically be multiple adapters for any one port, for various technologies that may plug into that port.
- Separate deployment from the domain (as Maven modules:
ear
,war
etc). - Installer is a kind of deployment, must be stupid.
- Cloud is a kind of deployment, must be decoupled.
- Only the app self should know how it could be installed/updated/removed.
- In build descriptor (
pom.xml
) mustn't be anything build-not-specific (no IDE configs etc.). - Think of service in Java as a set of abstract classes in one or more JAR files. Think of each new feature or feature extension as another JAR file that contains classes that extend the abstract classes in the first JAR files.
- Think of the tests as the outermost circle in the architecture. Nothing within the system depends on the tests, and the tests always depend inward on the components of the system.
Single System of Record: The idea is that any particular concept should originate in exactly one system. The hard part is getting all parts of the enterprise to agree on what those concepts actually are.
- Because of this coupling to the heavyweight container, isolated unit testing is difficult. It is necessary to mock out the container, which is hard, or waste a lot of time deploying EJBs and tests to a real server.
- Reuse outside of the EJB architecture is effectively impossible, due to the tight coupling.
- Even object-oriented programming is undermined. One bean cannot inherit from another bean.
- Across an organization, there are several definitions of every concept term (eg. Product, Consumer, ...).
Instead of creating a single system of record for any given concept, we should think in terms of federated zones of authority. We allow different systems to own their own data, but we emphasize interchange via common formats and representations.
Big Ball of Mud is haphazardly structured, sprawling, sloppy, duct-tape and baling wire, spaghetti code jungle.
Complexity: One reason for a muddled architecture is that software often reflects the inherent complexity of the application domain. Change: may cut directly across the grain of fundamental architectural decisions made in the light of the certainty that these new contingencies could never arise. Scale: Managing a large project is a qualitatively different problem from managing a small one
Throwaway code (Quick Hack) - You need an immediate fix for a small problem, or a quick prototype or proof of concept,
- Produce, by any means available, simple, expedient, disposable code that adequately addresses just the problem at-hand.
Keep it working - Maintenance needs have accumulated, but an overhaul is unwise since you might break the system.
- Do what it takes to maintain the software and keep it going.
Shearing layers - Different artifacts change at different rates.
- Factor your system so that artifacts that change at similar rates are together.
Sweeping it under the rug - Overgrown, tangled, haphazard spaghetti code is hard to comprehend, repair, or extend, and tends to grow even worse if it is not somehow brought under control.
- If you can’t easily make a mess go away, at least cordon it off.
Separates business logic from side effects by pushing those side effects to the edges of a business operation.
Maximizes the amount of code written in a purely functional (immutable) way, while minimizing code that deals with side effects.
Immutable means unchangeable: once an object is created, its state can't be modified. This is in contrast to a mutable object, which can be modified after been created.
All race conditions, deadlock conditions, and concurrent update problems are due to mutable variables. [a]
The separation between business logic and side effects is done by segregating two types of code:
- Code that makes a decision
- Code that acts upon that decision
OOP makes code understandable by encapsulating moving parts, FP makes code understandable by minimizing moving parts. (Michael Feathers)
Redefine the architects' role as the connecting element between the IT engine room and the business penthouse. [b]
- Understand and analyze requirements and constraints,
- design structures and relations
- define cross-cutting concepts,
- communicate architecture,
- evaluate architecture,
- check the integrity and help with implementation.
In detail:
- Write architecture documentation,
- search for an adequate, not perfect solution,
- define acceptance of the architecture (set constrains),
- moderate and coordinate at the boundaries (external interfaces),
- collect and analyze feedback,
- choose members of the team,
- choose tools and technologies,
- make technical decisions,
- analyze risks and escalates.
An architect speaks the language of the partner (a different language for developers or project managers).
Architects don't have to make all decisions by themselves, but they decide who decides.
- For example, if an interface design will be consumer- or provider-driven.
Enterprise architects must position themselves as the interface between strategic context and the inner delivery loop. [b]
- They translate the delivery teams' learning into a systematic view for IT strategy, while
- channeling the strategy to delivery teams with particular attention to security and costs.
The business not wanting to be involved in technical decision making because IT alone can't judge the value of an option. Instead, it's the architect job to translate technical options into meaningful choices for the business. [c]
Skill - The foundation for practicing architects. It requires knowledge and the ability to apply it to solve real problems.
Impact - The measure of how well an architect applies his or her skill to benefit the company.
Leadership - Determinates whether an architect advances the state of the practice.
- Hierarchical model
- Architect makes decisions outside of the team.
- Applicable to outsourcing.
- As a team member
- Architect could still be a bottleneck.
- Multiple architect roles in the team
- Responsibility of the architecture is shared, or
- team members are responsible for different parts of the architecture.
- Implicit architect
- Emergent, democratic.
- Could be ineffective and chaotic.
Architects
- advise the managers about risks. Managers
- manage risks,
- consult risks with architects.
Project | Product |
---|---|
Short-time | Long-time |
2 weeks to two months | 5 to 15 years |
Project managers | Architects |
Project goals | Quality goals |
There should be at least a small intersection between project and product goals. Both must be negotiated and agreed.
Architects and project managers typically work under different time horizons and thus value the same option differently. [c]
It's necessary for an architect to meet all stakeholders to understand all requirements on the system and documentation.
- Management, PMO, Business, QA, Testers, Operations, Auditor, Hotline, etc.
Everything you know, and everything everyone knows, is only a model.
System is not just any collection of things. A system is an interconnected set of elements that is coherently organized in a way that achieves something: elements, interconnections, and a function or purpose.
A system is more than the sum of its parts. I may exhibit adaptive, dynamic, goal-seeking, self-preserving, and sometimes evolutionary behavior.
- Systems can be nested within systems. Therefore, there can be purposes within purposes.
- Keeping sub-purposes and overall system purposes in harmony is an essential function of successful systems.
- A change in purpose changes a system profoundly, even if every element and interconnection remains the same.
The essential or defining properties of any system are properties of the whole which none of its parts have. Therefore, when a system is taken apart it loses its essential properties. A system is not the sum of the behavior of its parts, it’s a product of their interactions.
If we have a system of improvement that's directed at improving the parts taken separately you can be absolutely sure that the performance of the whole will not be improved. The performance of a system depends on how the parts fit, not how they act taken separately.
Systems need to be managed not only for productivity or stability, but they also need to be managed for resilience - the ability to recover from perturbation, the ability to restore or repair themselves.
System structure is the source of system behavior. System behavior reveals itself as a series of events over time.
- Structure is the key to understanding not just what is happening, but why.
- For any physical entity in a finite environment, perpetual growth is impossible. Ultimately, the choice is not to grow forever but to decide what limits to live within.
Systems often have the property of self-organization - the ability to structure themselves, to create new structures, to learn, diversify, and complexify. Even complex forms of self-organization may arise from relatively simple organizing rules - or may not.
Hierarchical systems evolve from the bottom up. The purpose of the upper layers of the hierarchy is to serve the purposes of the lower layers.
- The original purpose of a hierarchy is always to help its originating subsystems do their jobs better.
Disorderly, mixed-up borders are sources of diversity and creativity.
Efficiency - doing things right. Effectiveness - doing right things.
The definition of quality has to do with meeting or exceeding the expectations of the customer or the consumer. Therefore, if their expectations are not met, it’s a failure.
A defect is something that’s wrong. When you get rid of something that you don’t want you don’t necessarily get what you do want. And so, finding deficiencies and getting rid of them is not a way of improving the performance of a system.
Therefore, improvement must be directed at what you want, not at what you don’t want.
While continuous improvement is a good thing, it is not sufficient if you actually want to become a (market) leader in what you are doing. You need some bigger, creative steps for that - discontinuous improvement.
Quality ought to contain a notion of value, not merely efficiency. That’s a difference between efficiency and effectiveness. Quality ought to be directed at effectiveness.
The difference between efficiency and effectiveness is a difference between knowledge and wisdom, and unfortunately, we don’t have enough wisdom to go around. Until managers take into account the systemic nature of their organizations, most of their efforts to improve their performance are doomed to failure.
- Robert C. Martin: Clean Architecture [a]
- Robert C. Martin: Clean Code
- Mark Seemann: Dependency Injection in .NET
- http://alistair.cockburn.us/Hexagonal+architecture
- Sam Newman: Building Microservices [e]
- http://www.laputan.org/mud
- Michael T. Nyguard - Release it! (2nd edition)
- Joshua Bloch: How to Design a Good API and Why it Matters
- Carola Lilienthal: Sustainable Software Architecture
- Software Engineering at Google
- Donella Meadows: Thinking in Systems
- Gregor Hohpe: Cloud Strategy [b]
- Gregor Hohpe: The Software Architect Elevator [c]
- Allen Holub: Holub on Patterns [d]
- David Farley: Modern Software Engineering [f]
- Rest API best practices reloaded
- Simon Brown - Modular Monoliths
- Modular Monolith
- Modulithic applications with Spring https://github.com/odrotbohm/moduliths
- Clean Architecture with Java 11
- Reevaluating of Layered Architecture
- Vertical Slice Architecture
- The Trinity Architecture
- Simple made easy
- Simon Brown on Architecture
- Explicit Architecture
- Beyond the 12-factor app
- C4 Model
- System of systems
- Beyond the Rest
- Interfaces are not abstractions
- Architecture Decisions
- Complexity has to live somewhere
- Monolith vs Microservices (Tweet)
- Working vs good (Tweet)
- Cost of Architecture (Tweet)
- Flintstoning
- Don’t buy into the Technical Debt Illusion
- Fit Rest API URLs with signing
- Papers we love
- The continuous amnesia issue
- Things You Should Never Do
- Binding the Domain to the Spring Context with ComponentScan
- Choose Boring Technology
- The Devil’s Dictionary of Software Design (Humor)