This book provides a comprehensive overview of software architecture’s many aspects. Aspiring and existing architects alike will examine architectural characteristics, architectural patterns, component determination, diagramming and presenting architecture, evolutionary architecture, and many other topics.
Architects have an important responsibility to question assumptions and axioms left over from the previous eras.
Architecture characteristics are another dimension of defining software architecture: Availability, Reliability, Testability, Scalability, Security, Agility, Fault Tolerance, Elasticity, Recoverability, Performance, Deployability, Learnability.
8 core expectations placed on a software architect
- Make architecture decisions. Guide rather than specify technology.
- Continually analyze the architecture
- Keep current with the latest trends
- Ensure compliance with decisions
- Diverse exposure and experience
- Have business domain knowledge
- Possess Interpersonal skills
- Understand and navigate politics. Almost every decision an architect makes will be challenged. He must navigate the politics of the company and apply basic negotiation skills to get most decisions approved.
Unknown unkowns are the nemesis of software systems. This is why “Big Design Up Front” Software efforts suffer. All architectures become iterative because of unknown unknowns. Agile just recognizes this and does it sooner.
Building Evolutionary Architectures introduces the concept of using fitness functions to protect and govern architectural characteristics as change occurs over time. When designing an algorithm with a specific goal, developers must measure the outcome to see if it is closer or further away from an optimal solution.
Building Evolutionary Architectures co-opts the idea to create architectural fitness functions: an objective integrity assessment of some architectural characteristics. Architects always know the status of critical parts of the architecture because they have a verification mechanism in the form of fitness functions for each part.
Laws of software Architecture
- Everything in software architecture is a tradeoff. Corollary: If an architect thinks they have discovered something that isn’t a tradeoff, more likely they just haven’t identified the tradeoff yet.
- Why is more important than how
Architecture vs Design: Architect is responsible for analyzing business requirements to extract and define the architecture characteristics (“-ilities”), selecting which architecture patterns and styles would fit the problem domain, and creating components (building blocks of the system]. The artifacts created from these activities are then handed off to the development team, which is responsible for creating class diagrams for each component, creating UI screens, and developing and testing source code.
The issue with the traditional approach is that the architect is disconnected from the development teams, and as such the architecture rarely provides what it was set out to do. To make architecture work, architect and developer must be on the same team.
Architecture and design are a part of the circle of life of a s/w project.
As an architect, breadth is more important than depth.
Thinking like an architect is all about seeing trade-offs in every solution.
An architect needs to understand the business requirements and translate them into architecture characteristics.
Avoid being the bottleneck in development by not taking ownership of the most critical path of a project. Delegate most critical piece to the dev team, and pick up a noncritical feature, giving you exposure to using the design.
Architects can maintain hands on coding by
- Developing frequent proof of concepts
- Tackling Technical Debt or architecture stories.
- Working on bug fixes
- Creating command line tools and analyzers for the dev team. Could develop fitness functions.
- Code Reviews.
Software systems model complex systems, which tend towards entropy (or disorder). Architects must constantly expend energy to ensure good structural soundness.
Modularity: Logical grouping of related code.
3 concepts to understand and measure modularity: Cohesion, coupling and connascence.
Cohesion: To what extent the parts of the module should be contained within the same module. It’s a measure of how related the parts are to one another. Ideally, a cohesive module is one when all the parts should be packaged together, because breaking them into smaller pieces would require coupling the parts together via calls b/w modules to achieve useful results. Attempting to divide a cohesive module would only result in increased coupling and decreased readability.
- Lcom: metric to measure cohesion. The sum of set of methods not shared via sharing fields.
Cohesion measures-best to worst
- Functional cohesion: Every part of the module is related to other, and the module contains everything needed to function.
- Sequential Cohesion: Two modules interact, where one outputs data that becomes input to the other.
- Communicational Cohesion: Two modules form a communication chain, where each operates on information and/or contributes to some output.
- Procedural cohesion: Two modules execute code in a particular order.
- Temporal cohesion: Modules are related based on timing dependencies. For eg., many systems have a list of seemingly unrelated things that must be initialized at system startup; these system tasks are temporally cohesive.
- Logical Cohesion: The data within modules is related logically, but not functionally. E.g string Utils Package.
- Coincidental Cohesion: Just in the same source file, not related. Worst kind.
- Afferent Coupling measures the number of incoming connections.
- Efferent coupling measures the number of outgoing connections.
- Abstractness is the ratio of abstract artifacts ( abstract classes, interfaces, so on) to concrete artifacts (implementation).
Instability= Efferent Coupling/ Efferent+ Afferent coupling. If a class calls many other classes, it’s brittle and unstable.
Connascence: Two components are connascent if a change in one would require the other to be modified in order to maintain the overall correctness of the system.
Static Connascence: Refers to source code level coupling. It is a refinement of the afferent and efferent coupling.
- Connassena of name: Multiple components must agree to the name of an entity. E.g name of methods.
- Connascence of Type
- Connascene of Meaning: hard coded numbers
- Connasence of Position: order of values
- Connascence of Algorithm
Dynamic Connascence: Analyzes calls at runtime.
- Connascence of Execution: order of execution is important.
- Connascence of Timing
- Connascence of Values: Several values relate on one another and must change together.
- Connascene of Identity: Multiple components must reference the same entity.
- Strength-The ease with which a developer can refactor the type of coupling. Static connssence is preferable to dynamic because developers can determine it by simple source code analysis.
- Locality: How proximal the modules are to each other in the codebase.
- Degree: Size of its impact - A few classes or many?
3 Guidelines for using connaseance to improve systems modularity.
- Minimise overall connascence by breaking the system into encapsulated elements.
- Minimize any remaining connascence that crosses encapsulation boundaries.
- Maximize the connascence within encapsulation boundaries.
Architects are responsible for detailing, defining and analyzing all the things software must do that isn’t directly related to the domain functionality (architecture characteristics).
An architecture characteristic:
- Specificies a non domain design consideration
- Influences some structural aspect of the design
- Is critical or important to application success
E.gs of Architecture Characteristics
Operational Architecture Characteristics
- Availability: If 100% needed, need quick recovery from failure
- Continuity: Disaster recovery capability.
- Performance: Stress testing
Structural Architcture Characteristics: Code structure and quality concerns
- Localization: multiple languages
- Supportability: Technical Support/Logging.
Cross Cutting Architecture Characteristics
- Privacy: hide transactions from internal embloyees
Architects rarely encounter the situation when they’re able to design a system and maximize every single architecture characteristic. Never shoot for the best architecture, but rather the least worst architecture.
Design architectures to be as iterative as possible
Extracting Architecture Characteristics from Domain Concerns:
- Keep the final list as short as possible.
- Have the domain stakeholders select the top 3 most important characteristics from the final list.
Translation from Domain Concerns to Architecture characterates
- Mergers& Acquisitions-> Interoperability, scalability, adaptability, extensibility.
- Time to market-> Agility, testability, deployability
- User satisfaction-> Performance, availability, fault tolerance, testability, deployability, agility, security
- Competitive Advantage-> Agility, testability, deployability, scalability, availability, fault tolerance
- Time and budget-> Simplicity, feasibility.
Extracting Architecture Characteristics from Requirements
- Explicit Characteristics: Appear in requirements specification.
- Scalability vs Elasticity: Elasticity needs to support burst of users as well.
- Performance: We must establish a baseline for performance w/o particular scale, as well as determine what an acceptable level of performance is given a certain number of users.
- Implicit Characteristics: not specified in requirement documents. E.g availability, security reliability.
There are no wrong answers in architecture, only expensive ones.
High level teams don’t just establish hard performance numbers, they base their definitions on statistical analysis. Engineers measure the scale overtime and build statistical models, then raise. alarms if the realtime metrics fall outside the prediction models.
Fitness Function: An object function used to assess how close the output comes to achieving the aim.
Architecture Fitness Function: Any mechanism that provides an objective integrity assessment of some architecture characteristic or combination of architecture characteristics.
When evaluating many operational architecture characteristics, an architect must consider dependent components outside the code base that will impact those characteristics.
Connascence: Two components are connascent if a change in one would require the other to be modified in order to maintain the overall correctness of the system.
- Static: Discoverable via static code analysis
- Synchronous: Caller waits for response from Callee
- Asynchronous: Allows fire & forget semantics in event driven architecture
Architecture Quantum: An independently deployable artifact with high functional cohesion and synchronous connascence.
Components are the physical manifestation of a module.
Software architecture is usually independent of software development process (like Agile, Waterfall)
Before an architect can identify components, they must know how to partition the architecture.
Two types of top level partitioning in an architecture
- Technical partitioning: e.g MVC
- Domain partitioning: e.g Modular monolith.
Conways Law: Orgs which design systems, are constrained to produce designs which are copies of the communication structures of these orgs.
Inverse Conway Maneuver: Evolve teams and org structure to promote the desired architecture.
Domain Partitioning: Separate top level components by workflows and/or domains.
- Modeled more closely towards how the business functions
- Easier to utilize the Inverse Conway Maneuver to build cross-functional teams around domains.
- Aligns more closely to the modular monolith and micro services architecture styles.
- Message flow matches the problem domain.
- Easy to migrate data and components to distributed architecture.
- Customization code appears in multiple places.
- separates customization code clearly
- Aligns more closely to the layered architecture pattern.
- Higher degree of global coupling.
- Devs have to duplicate domain concepts ‘in multiple layers.
- Higher coupling at a data level. Single shared database.
While there is no one true way to design components, a common anti pattern is the entity trap. This isn’t an architecture, it’s an ORM of a framework to a database.
Actor/Actions approach can be used to map requirements to components. Architects identify actors who perform activities with the application and the actions these actors may perform.
Event storming as a component discovery technique comes from DDD and shares popularity with microservices. The architect assumes the project will use messages and/or events to communicate b/w the various components.
Workflow Approach: Models the component around workflows.
Monolithic vs Distributed Architecture: Monolithic architecture features a single deployable unit, including all functionality of the system that runs in the process, typically connected to a single database (Types: Layered & modular monolith). In a distributed architecture, the application consists of multiple services running in their own ecosystem, communicating via networking protocols.
Architecture style is the overarching structure of how the user interface and backend code are organized. Architecture patterns, are low level design structures that help form specific solutions within an architecture style (e.g high scalability or performance)
- Big Ball of Mud: Absence of any architecture. Suffers from problems in deployment, testability, scalability & performance.
- Unitary Architecture: s/w exists on one machine.
- Desktop + Database Server
- Browser + web Server
- Three-tier: Browser + App server + DB.
Fallacies of Distributed Computing
- The Network is reliable: need timeouts and circuit breakers.
- Latency is zero
- Bandwidth is Infinite: Communication b/w components in distributed architecture significantly utilizes bandwidth, causing networks to slow down, thus impacting latency.
- The network is secure
- The topology never changes.
- There is only 1 administrator
- Transport Cost is zero.
- The network is homogenous
Other Distributed Considerations
- Distributed Logging: Have to consolidate logs from different systems.
- Distributed transactions: Rely on eventual consistency.
- Contract maintenance and versioning
- Layered Architecture is a very natural/default way to develop apps due to Conway’s Law.
- Components are organized into logical horizontal layers, with each layer performing a specific role within the app.
- 4 standard layers: Presentation, business, persistence and database.
- Layers of isolation: Changes made in one layer of the architecture don’t impact or affect components in other layers, provided the contract b/w those layers remain unchanged. To support this, layers have to be closed, i.e a request moves top down, and cannot skip any layers. This allows any layer to be replaced w/o impacting any other layer.
- Architecture sinkhole antipattern: If most of the requests are just passing through the layers, w/o any additional processing, then layered architecture might not be the right choice.
- Strengths of layered architecture: overall cost & simplicity.
Pipeline Architecture Style
- Pipes & Filter architecture: Underlying principal b/w Unix terminal shell languages.
- Pipes form the communication channel b/w filters. Each pipe is typically unidirectional and point to point for performance reasons, accepting input from one source and always directing output to another.
Fitters are self contained, independent from other filters, & generally stateless. Composite tasks should be handled by a sequence of filters rather than a single one. 4 types of filters:
- Transformer( like map)
- Tester (like reduce)
- Strengths-Cost, Simplicity
Microkernel Architecture style
- Plug-in Architecture.
- A monolithic architecture consisting of 2 components: a core system, and plug-in components.
- Core System: minimal functionality to run the system. Depending on complexity/size, core system can be implemented as a layered architecture or a modular monolith
- Plugin Components: are standalone, independent components that contain specialized processing, additional features and custom code meant to enhance the system. Plugin components should be independent of each other. Can be integrated at compile time, or run time.
- Plugins can communicate directly, or via REST/ messaging to core system. Remote access provides bette decoupling, scalability& thoughbot.
- Remote plugin turns the microkernel architecture into a distributed architecture rather than a monolithic one, making it difficult to implement & deploy.
- Plugin components should not connect directly to DB, but use core to do it. Gives us Decoupling. Making a DB change should impact only the core
- Registry: Core system needs to know about which plug-in modules are available and how to get to them. Registry contains information about each plug-in module, including things like data contract, name & remote access protocol details.
- Strengths: simplicity & overall cost.
- Weakness: Scalability, Fault tolerance
- Can be both domain partitioned, and technical partitioned.
Service Based Architecture
- A distributed layered structure consisting of a separately deployed UI, separately deployed remote coarse gained services, and a monolithic DB.
- API layer can have proxy or gateway to allow UI to access services, or UI can use service locator pattern.
- DB can be split by service, if needed. But prevent calls b/w services to exchange date.
- A domain service must contain some sort of API facade that the UI interacts with to execute business functionality.. The facade orchestrates the request. With SOA, its internal class level orchestration, & with microservices, its external service orchestration.
- SOA use ACID (atomicity, consistency, isolation, durability) DB transactions involving commits & rollbacks to ensure date integrity within a single domain service. Distributed architectures like microservices use distributed transaction technique known as BASE transactions (basic availabilty, soft state, eventual consistency) that rely on eventual consistency.
- DB partitioning: A table schema change can potentially affect each service. Especially if all services are using the same custom library for entity objects to talk to DB. Can mitigate this by logical partitioning of DB, & propogating that to multiple shared libraries.
- Strengths: agility, deployability, test coverage. Very pragmatic.
SOA doesn’t need a lot of choreography or orchestration
- Orchestration: coordination of multiple. Services through a separate mediator service
- Choreography-coordination whee each service talks to one another w/o a central mediator.
Event Driven Architecture Style
- Decoupled event processing events that asynchronously receive and process events. Can be used standalone, or embedded within other styles.
2 primary topologies
- Mediator topology: used when you require control over the workflow of an event processor
- Broker topology: when you require a high degree of responsiveness and dynamic control over the processing of an event.
- No central event mediator. Message flow is distributed across the event processor components in a chain-like broadcasting fashion though a lightweight message token (Rabbitmq). Useful when you have a relatively simple event processing flow and you do not need central event orchestration and coordination.
- Initiating event: initial event that starts the entire flow
- Event Broker: Event sent to channel in event broker
- Event Processor: A single event processor accepts the event and works on it
- Processing Event: Event processor asynchronously advertises to the rest of the system what it did. This is sent to event broker for further processing, if needed.
- Performance, responsibility & scalability are all great benefits of the broker topology.
- Very dynamic based on situation. Noone knows when work is done.
- Error handling is hard. Since there is no mediator monitoring/controlling, if a processor crashes, no one knows about it
- Recoverability: hard to restart
- Event mediator manages and controls the workflow for initiating events that require the coordination of multiple processes.
- Components are event queue, event mediator, event channels and event processor.
- Event processors do not advertise what they did to the rest of the system.
- Multiple mediators to reduce single point of failure & increase throughput & performance.
- Mediator component has knowledge & control over the workflow, & can maintain event state and manage error handling, recoverability & restart handling.
- In broker topology, processing events signify something has occurred in the system. In mediator processing, events are commands, that need to occur, as opposed to event.
- Disadvantages: more coupling of event processor, lower scalability & performance
- Asynchronous Capabilities: lets the app be responsive to the user. Error handling is more complicated.
- Workflow Event patterns to handle error handling. Leverages delegation, containment & repair through the use of a workflow delegate. If event consumer experiences error while processing, it delegates that error to workflow processor. Workflow processor determines error, & how to handle it. It repairs data & sends back to original queue. If it cannot handle, it sends it to a dashboard, where its manually fixed.
Preventing Data Loss
- Leverage persistent message queues
- Use client acknowledge mode to process events from queue
- LPS: Last participant support.
Request Reply messaging
- Can use correlation IP in request/response.
- Or use temporary queue.
Disadvantages of event driven architecture
- Noisy Logs. Hard to see overview
- Strengths: performance, Scalability, Fault tolerance, evolutionary.
- Weakness-Simplicity, Testability.
Space based Architecture style.
- Most web based applications follow same general request flow. Request from browser → web server → Application Server → Database.
- For high user load, scaling web server is easier, app server is harder, & DB is hardest.
- DB is the final limiting factor in how many transactions you can process concurrently.
- Space based architecture is designed to address problems involving high scalability, elasticity and high concurrency issues. Also useful for apps that have variable & unpredictable user volumes.
- Achieved by removing central DB as a synchronous constraint. Application date is kept in memory & replicated among all the processing units. When a processing unit updates data, it asynchronously sends the data to the DB, usually via messaging with persistent queues. Processing units start up & shut down dynamically as user load increases & decrease.
- Processing Unit: contains the application logic (web based component as well as the backend logic). Also contain an in-memory data grid and repication engine.
Virtualized Middleware: handles the infrastructure concerns within the architecture that control various aspects of data synchronization, and request handling. Its components:
- Messaging Grid: Manages input request and session state. When request comes into virtualized middleware, the messaging grid component determines which active processing components are available to receive the request and forward the request to one of the processing unit.
- Data Grid: Most important component. Each processing unit should contain the same data in its in-memory grid. Data is synchronized b/w processing units that contain the same named data grid. A processing unit can contain as many replicated caches as needed to complete its work. Alternatively, One processing unit can make a remote call to another processing unit to ask for date or leverage the processing grid to orchestrate the request. Data replication within the processing units also allows service instances to come up and down w/o having to read data from the DB, providing there is at least one instance containing the replicated cache.
- Processing Grid: Optional component that manages orchestrated request processing when there are multiple processing units involved in a single business request.
- Deployment Manager: Manages, dynamic startup and shutdown of processing unit instances based on local conditions.
Data Pumps: way of sending data to another processor which then updates date in a DB. Implemented using messaging-supports async communication, guaranteed delivery and preserving message order through FIFO queuing. Provides decoupling b/w processing unit & data writer. Can have multiple date pumps by domain, subdomain, type of cache.
- Have associated contracts, including an action associated with contract date( add, delete or update)
- Date Writers: Accepts messages from a data pump and updates the DB with the info contained in the message.
Date Readers: Read Data from the DB and send it to the processing units via a reverse date pump. Invoked under 1 of 3 situations:
- a crash of all processing unit instances of the same named cache
- redeployment of all processing units within the same named cache
- retriving archive data not contained in the application cache. When instances of class of processing unit is coming up, 1st one to get the lock becomes temporary cache owner. Others wait. As date is read, its sent a different queue (reverse data pump). One processing unit gets the data, it loads the cache.
- Data Collisions: Due to update latency, 2 cache instances might update date at same time. This a formula to calculate this, based on number of instances, update rate, cache size & latency.
- Can be cloud based, on-premise, or hybrid.
Replicated vs Distributed Caching.
- Replicated: Each processing unit contains its own in-memory date grid that is synchronized b/w all processing units using the same named cache. When update occurs within any of the processing units, the other units are automatically notified. Fast, high tolerance, no single point of failure. Not possible to use for high date volumes, and high update rates
- Distributed caching: External service or server to hold a centralized cache. Less Performance, but high level of date consistency because all data in one place.. For date that doesn’t change often, use replicated cache. For highly consistent data, use distributed cache.
- Near Cache: hybrid model bridging in-memory date grids with a distributed cache. Front cache contains small subset of data from full cache.
- Space based architecture is well suited for apps that experience high spikes in user request volume, & throughput in excess of 10000 concurrent users. E.g online concert ticketing system & online auction systems. Number of processing units can be increased ahead of spike.
- Strengths: Elasticity, scalability & performance.
- Weakness-Testability, cost
Orchestration Driven SOA
- Came about in late 90s. Operating System licenses were expensive. Hard to reuse.
- Business Service: Entry point. No code, just input, output and schema info.
- Enterprise Services: Contain fine grained shared implementations. These services are building blocks that make up coarse grained business services, tied together via the orchestration engine.
- Application services: One off, single implementation services.
- Infrastructure services: supply operational concerns, such as monitoring, logging, authentication, authorization.
- Orchestration Engine: forms the heart. Stitching together business Service implementations using orchestration, transactional coordination & message transformation. This is the heart of the architecture, so this team becomes a political force & a bureaucratic bottleneck.
- Because of emphasis on reuse, huge amount of coupling b/w components.
- Most technially partitioned general purpose architectee ever attempted.
Micro Services Architecture
- Heavily inspired by DDD, especially Bounded Context.
- In traditional monolithic architectures, developers would share entities & behaviors, identified in code & DB schemas.
- Within a bounded context, the internal parts, such as code & date schemas, are coupled together to produce work; but they are never coupled to anything outside the bounded context, such as DB ow class definition from another bounded context.
- The negative tradeoff of reuse is coupling. When aiming for decoupling, duplication is better then reuse. Primary goal of microservices is high decoupling, physically modelling the logical notion of bounded context.
- Distributed: Each service runs in its own process, using cloud & containers. Downside is performance. Network calls take much longer than method calls, and security verification at every endpoint adds additional processing time. Avoid transactions across service boundaries, making determining the granularity of services the key to success in this architecture.
- Bounded Context: Prefer duplication to coupling. Each service includes everything necessary to operate within the application, including classes
Granularity: Microservice is a label, not a description. In some applications, the natural boundaries might be large for some parts of the system. Some processes are more coupled than others. Guidelines to help find boundaries:
- Purpose: Each microservice should contribute one significant behavior on behalf of overall application.
- Transactions: Entities that need to cooperate in a transaction show architects a good service boundary.
- Choreography: Avoid extensive communication overhead Iteration is the only way to ensure good design.
- Data Isolation: Relational DB to unify values for a single source of truth is no longer an option. Either identity one domain as the source of truth or use DB caching or replication to distribute info. Creates headache, but also opportunities to choose the right tool for each team.
- API Layer: sits b/w consumer (Ul, other systems). Good location to perform useful tasks, either via indirection as a proxy or a tie into operational facilities, such as a naming service. Should not be used as a mediator or orchestration tool, since all interseting logic should be inside a Bounded Context.
- Operational Reuse: Some part of architecture, such as monitoring, logging and circuit breakers, could benefit from coupling/reuse. Use sidecar pattern to handle all opreation concerns together& put in a shared component.
- Architects use service discovery as a way to build elasticity into microservices architecture. Rather than invoke a single service, a request goes through a service discovery tool, which can monitor the number & frequency of requests, as well as spin up new instances of services to handle scale or elasticity conceine.
Frontends: 2 styles of UI
- Monolithic UI: Single UI that calls through API layer to satisfy user requests.
- Micro Frontends: Each Service emits the UI for that service, which the frontend then coordinates with the other emitted UI.
Communication: Can be synchronous or asynchronous. Microservice architectures typically utilise protocol- aware heterogenous interoperability.
- Protocol Aware: Each service must know which protocol to use to call other services.
- Heterogenous: supports polyglot environment.
- Interoperability: Services call other services to collaborate and send/receive info.
- Async communication uses events & messages. Broker & mediator patterns manifest in microservices as choreography & orchestration.
- Choreography: No central coordinater exists. Same as broker event drixen architecture.
Transaction & Sagas: Atomicity that is trivial in monolithic applications becomes a problem in distributed ones. Better to fix granularity of services rather than implement cross service transactions.
- If you still need it, use Saga pattern: Service acts as a mediator across multiple service calls and coordinates the transactions. In case of errors, mediator sends request to undo previous request (Compensating transaction framework). Implemented by each request entering pending state until mediator indicates overall sucess. Another alternative is to build undo/ redo functionality.
- Strengths: High scalability, elasticity and evolution.
- Low performance because of high network calls.
New architecture designs reflect specific deficiencies from past architecture style.
Decisions to make when choosing the Appropriate Architecture Style
- Monolith Vs Distributed
- When should data live
- What communication style b/w services- sync or async. Use Sync by default.
Modular monolith: builds domain centric components with a single DB, deployed as a single quantum.
Making architecture decisions involves gathering relevant info., justifying the decisions, documenting the decision, and effectively communicating the decision to the right stakeholders.
Anti-patterns for making Architecture Decisions
Covering your Assets Anti Pattern: Avoiding decision out of fear of making the wrong decision. Two ways-to avoid:
- Wait till last responsible moment to make decision.
- Continually collaborate with dev teams to ensure that the decision you made can be implemented as expected.
- Groundhog Day Pattern: People don’t know why a decision was made, so it keeps on being discussed over and over. When justifying architecture decisions, it is important to provide both technical and business justifications for your decision. Four common business justifications include cost, time to market, user satisfaction, and strategic positioning.
- Email Driven Architecture Anti-Pattern: Email is a poor document repository system. Only mention nature and content of the decision in the body of the email and provide a link to the single system of record for the actual architecture decision and corresponding details. Second rule is to only notify people who really care about the decision.
Architecturally significant decisions are those that affect the structure, non-functional characteristics, dependencies, interfaces, or construction techniques.
- Structure: impacts the patterns or styles of architecture used
- Non-Functional Characteristics
- Interfaces: how services are accessed and orchestrated
- Construction Techniques: Decisions about platforms, frameworks, tools, processes.
Architectural Decision Records: One or two pages describing an architecture decision.
- Status: Proposed/Accepted/Superseded/RFC
- Content: What situation is forcing me to make this decision
- Decision: along with justification.
- Consequences: Impact, tradeoffs.
- Compliance: how it will be measured and governed. Can a fitness function be used?
- Notes: Includes author and dates.
ADRs can be stored in a git repo, or a wiki. They provide an effective means to document a software architecture.
Analyzing Architecture Risk
- Risk matrix: Use two dimensions to qualify risk: the overall impact of the risk, and the likelihood of risk occurring. Each dimension has a low 1), medium (2), and high (3) rating. Multiply to get objective numerical number representing the risk.
- Risk assessment: Summarized report of the overall risk of an architecture w.r.t some meaningful and contextual assessment criteria. Quantified risk can be accumulated by risk criteria and also by service or domain area. Can show direction of risk using arrows, or +/-, along with numerical change, and color-red/green.
Risk storming: Collaborative exercise to determine architectural risk within a specific dimension. Involves individual part, and collaboratia part. Th individual part, all participants individually assign risks to areas of the architecture using the risk matrix ( so participants don’t influence one another). In collaboration part, all work together to gain consensus and form solutions for risk mitigation. 3 steps.
Diagramming and presenting Architecture.
- Using layers in a diagramming tool allows architects to incrementally build pictures for presentations.
- Diagramming Standards-UML, CY, Archimate.
- Diagramming Guidelines: Solid Line: Synchronous Communication, Dotted Line: Async
- Slides should not be all of speaker notes. Insert blank slide to get attention back to speaker.
Making Teams Effective
- An effective slw architect strives to provide the right level of guidance and constraints to the team.. Architects that create too many constraints cause frustration within team, and developers leave for happier environments. Loose constraints force the team to take on the role of a s/w architect, performing proof of concepts and battling over design decisions without the proper level of guidance.
- Control Freak Architect: Control every detailed aspect. Result in frustration and lack of respect.
- Armchair Architect: Hasn’t coded in a long time and doesn’t take implementation details into account.
- Effective Architect: Produces appropriate constraints and boundaries on the team.
5 factors to decide how much conted to exert:
- Team familiarity: The better the team members know each other, the less control is needed.
- Team size: the large the team size, the more control is needed.
- Overall Experience: Teams with senior devs require less control.
- Project complexity: More control for more complex projects
- Project Duration: Shorter the project, less control needed,
- Can assign +/- 20 points to each of criteria above, and the total score to decide whether to lean towards armchair architect, or control freak.
Three factors come into play when considering the most effective development team-size:
- Process Loss: The more people you add to a project, the more time the project will take. Actual productivity is less than group potential, the difference being the process loss of the team. One indication of process loss is frequent merge conflicts. Looking for areas of parallelism & having team members work on separate services is one way to avoid process loss.
- Pluralistic Ignorance: Everyone agrees( but privately disagrees) a norm because they think that they are missing something obvious.
- Diffusion of Responsibility: As the team size increases, it has a negative impact on communication.
- Checklists: Error prone processes are good candidates.
- Hawthorne effect: If people know they’re being monitored, their behavior changes.
Negotiation& Leadership skills.
- Calm leadership combined with clear and concise reasoning will always win a negotiation.
- When convincing developers to adopt on architecture decision, provide a justification.
- 50% of being an effective software architect is having good people skills, facilitation skills, and leadership skills.
- Essential vs Accidental complexity: Essential complexity is part of the situation. We have to work with it. E. g- 99.999% uptime. Accidental Complexity is when we added complexity that could’ve been avoided. An effective way to avoid accidental complexity is 4C’s of architecture: Communication, Clarity, Conciseness and Collaboration.
- A good software architect is one that strives to find an appropriate balance between being pragmatic while still applying imagination and wisdom to solying problems.
Devote 20 minutes a day to learning something new.
I read this book as part of a book club at work. I have 20 years of industry experience, and found the book to be very insightful. It also helps in developing a shared vocabulary to communicate better with other developers.