{"id":16434954,"url":"https://github.com/janmarkuslanger/software-architecture-guide","last_synced_at":"2026-04-05T17:38:26.710Z","repository":{"id":43323502,"uuid":"448321697","full_name":"janmarkuslanger/software-architecture-guide","owner":"janmarkuslanger","description":"Software Architecture Guide","archived":false,"fork":false,"pushed_at":"2023-01-29T18:28:43.000Z","size":858,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-11-10T22:03:25.301Z","etag":null,"topics":["design-patterns","software-architecture","software-engineering","solid","uml"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/janmarkuslanger.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-01-15T15:53:39.000Z","updated_at":"2023-09-06T19:40:22.000Z","dependencies_parsed_at":"2023-02-16T00:31:17.417Z","dependency_job_id":null,"html_url":"https://github.com/janmarkuslanger/software-architecture-guide","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janmarkuslanger%2Fsoftware-architecture-guide","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janmarkuslanger%2Fsoftware-architecture-guide/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janmarkuslanger%2Fsoftware-architecture-guide/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janmarkuslanger%2Fsoftware-architecture-guide/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/janmarkuslanger","download_url":"https://codeload.github.com/janmarkuslanger/software-architecture-guide/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":233011216,"owners_count":18611078,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["design-patterns","software-architecture","software-engineering","solid","uml"],"created_at":"2024-10-11T08:50:35.342Z","updated_at":"2025-12-27T11:37:30.968Z","avatar_url":"https://github.com/janmarkuslanger.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eA software architecture guide\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"head.webp\" alt=\"Dog assembling building blocks\" width=\"300\" /\u003e\u003c/p\u003e\n\n---\n\n# Introduction 👋\n\nThis guide aims to provide a concise overview of various aspects of software architecture. It covers key concepts, architectural styles, and essential principles to help people understand the foundational elements of building software systems.\n\n## What is Software Architecture?\n\nThere is no clear and universally accepted definition of software architecture, as it heavily depends on the context and objectives of a system. Generally, software architecture describes the entirety of decisions that shape the design, structure, and behavior of a software system. These decisions can be made consciously or unconsciously.\nRegardless of whether these decisions are deliberate or accidental, every software project inevitably develops a software architecture. The quality and clarity of this architecture significantly impact the maintainability, extensibility, and scalability of the system.\n\n---\n\n# Key concepts 🔑\n\n## Coupling\n\nCoupling describes the degree of dependency between two components in a system.\nIn general, we aim for a system with as **loose coupling** between components as possible. \nA component can represent various entities, such as a module, a service, or a database. \n\nDependencies can manifest in many forms and affect the flexibility and maintainability of the system.\n\n### Types of coupling \n\n#### Temporal Coupling\n\nBuilding blocks are dependent on the timing of their execution. \n\n#### Data Coupling\n\nBuilding blocks are connected by exchanging data.\n\n#### Content Coupling\n\nA building block directly accesses or manipulates internal data of another building block. \n\n#### Control Coupling\n\nA building block controls the behavior of another by passing control data. \n\n#### Contextual Coupling\n\nBuilding blocks depend on a shared context (for example configuration).\n\n#### Common Coupling\n\nBuilding blocks share the same global resources.\n\n#### Sequential Coupling\n\nThe output of one building block serves as the input for another building block.\n\n#### External Coupling\n\nA building block depends on external systems, services, or interfaces.\n\nExamples of Coupling:\n\n- Component A imports another component B: This creates a direct dependency where A relies on the functionality or structure of B. If B changes, A might also need to be updated.\n- A component depends on receiving data from a REST API: The component must wait for the API's response before it can process or proceed. This introduces a dependency on external communication and response times.\n\n## Cohesion\n\nCohesion describes how strongly components within a module or system are related. In general, we aim to achieve high cohesion.\nHigh cohesion ensures that components are focused on a single responsibility, making the system easier to understand, maintain, and extend.\n\n---\n\n# Architecture Styles 🏭\n\n## Monolithic Architecture\n\nMonolithic Architecture is an architectural style where the entire system is built as a single, unified block. \nAll components, such as the user interface, business logic, and data access layer, are tightly integrated and operate as a single application.\n\n## Microservices\n\nA microservice is an architectural style where a system is composed of multiple independent services.\nEach service is designed to perform a specific function and operates autonomously, communicating with other services through well-defined APIs.\n\n## Event-Driven Architecture\n\nEvent-Driven Architecture is a software architectural pattern where the system state is determined by events. \nAn event represents a change in the system. Building blocks can listen to those events.\n\n## Service-Oriented Architecture (SOA)\n\nService-Oriented Architecture (SOA) is a software architectural style where applications are built by services.\nEach service represents a discrete functionality and communicates with other services through well-defined interfaces.\n\nSOA and Microservices share many similarities. \nHowever, in Microservices, the services are significantly smaller, whereas in SOA, the services are much larger and provide multiple functionalities within a specific domain.\n\n## Serverless Architecture\n\nServerless architecture is a cloud computing model where the cloud provider dynamically manages the allocation of resources.\nDevelopers focus on writing and deploying code without worrying about managing or provisioning servers.\n\n## Cloud Architecture\n\nCloud architecture refers to the design and deployment of applications and services that run on cloud infrastructure provided by cloud providers.\n\n---\n\n# Design Patterns 🏁\n\nDesign Patterns are reusable solutions to common problems in software development. \nThey serve as blueprints that help developers create software that is more efficient, flexible, and maintainable.\n\n\n## Structural Patterns\n\n**Structural Patterns are solutions to organize classes and their objects**\n\n---\n\n### Adapter \n\nThe adapter patterns enables two incompatible interfaces to work together.\nIn the Adapter Pattern, an incompatible interface is wrapped by an adapter, and the corresponding methods and more are adapted to the target interface.\n\n\u003cdetails\u003e\n  \u003csummary\u003eExample\u003c/summary\u003e\n  \nImagine you have a workout application that operates using kilograms as the unit of measurement. In this application, there's a class called `Workout` with a method named `addExercise`, which takes the weight in kilograms as input.\nNow, you want to extend the app to also support weights in pounds. However, the existing client method, `buildWorkout`, is designed to work exclusively with kilograms. To address this, you introduce a new class called `PoundWorkout`, which also has a method named `addExercise` but expects the weight in pounds.\nTo bridge the gap between the `PoundWorkout` class and the existing client logic, you create an adapter class called `Adapter`. This adapter takes an instance of the incompatible `PoundWorkout` class as an argument. It provides its own implementation of the `addExercise` method, which internally calls the `addExercise` method of the `PoundWorkout` class after converting the weight from kilograms to pounds.\nThis way, the client can seamlessly use the adapter without any need to modify or restructure the existing codebase. The adapter handles the conversion and ensures compatibility between the two systems.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eCode Example\u003c/summary\u003e\n\n  ```python\n  class Workout:\n    def add_exercise(self, name, weight_kg):\n      print(f\"Added exercise: {name}, Weight: {weight_kg} kg\")\n\n  class PoundWorkout:\n    def add_exercise(self, name, weight_lb):\n      print(f\"Added exercise: {name}, Weight: {weight_lb} lbs\")\n\n  class Adapter:\n    def __init__(self, pound_workout):\n      self.pound_workout = pound_workout\n  \n    def add_exercise(self, name, weight_kg):\n      weight_lb = weight_kg * 2.20462\n      self.pound_workout.add_exercise(name, weight_lb)\n  \n  def build_workout(workout):\n    workout.add_exercise(\"Bench Press\", 100) \n    workout.add_exercise(\"Deadlift\", 130) \n  \n  print(\"using regular kg\")\n  workout = Workout()\n  calculate_volume(workout)\n  \n  print(\"using pounds\")\n  pound_workout = PoundWorkout()\n  adapter = Adapter(pound_workout)\n  calculate_volume(adapter)\n```\n\u003c/details\u003e\n\n---\n\n### Bridge \n\nThe Bridge Pattern provides a solution to decouple abstraction from implementation by using object composition, allowing both to evolve independently for related classes.\n\n\u003cdetails\u003e\n\n  \u003csummary\u003eExample\u003c/summary\u003e\n\nImagine you have a car that you want to build. For the car there might be different engines available. \nYou could create multiple subclasses like `ElectricCar` or `PetrolCar`. But now we want to add gear-shift to the cars. \nThis would lead into more subclasses like `ElectricManuelGearCar`, `ElectricAutomaticGearCar`, `PetrolManuelGearCar` and `PetrolAutomaticGearCar`.\nWe can fix this by switching from inheritance to composition. Our class `Car(engine: Engine, gearshift: Gearshift)` expects the abstractions `Engine` and `Gearshift`. \nThen we create our implementations `AutomaticGear`, `ManuelGear`, `PetrolEngine` and `ElectricEngine`. \nNow we can create our cars like Car(new AutomaticGear(), new Petrol()).\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eCode Example\u003c/summary\u003e\n\n  ```python\n\n  class Gearshift:\n    def start(self):\n        raise NotImplementedError\n\n  class ManuelGear(Gearshift)\n    def start(self):\n      print(\"Start manuel\")\n\n  class AutomaticGear(Gearshift)\n    def start(self):\n      print(\"Start automatic\")\n\n  class Engine:\n    def start():\n      raise NotImplementedError\n\n  class PetrolEngine(Engine):\n    def start(self):\n      print(\"Start petrol engine\"\n\n  class ElectricEngine():\n    def start(self):\n      print(\"Start electric engine\"\n\n  class Car():\n    def __init__(self, engine, gearshift):\n      self.engine = engine\n      self.gearshift\n\n    def drive(self):\n      self.engine.start()\n      self.gearshift.start()\n\n\n    petrol = PetrolEngine()\n    manuel = ManuelGear()\n    car = Car(petrol, manuel)\n    car.start()\n\n  ```\n\u003c/details\u003e\n\n---\n\n## Behavioral Patterns\n\n**Behavioral Patterns are solutions to define interaction between objects**\n\n## Creational Patterns\n\n**Behavioral Patterns are solutions to create objects**\n\n---\n\n### Factory method\n\nThe Factory Method encapsulates object creation in a method that subclasses override to decide which concrete object to instantiate.\n\n\u003cdetails\u003e\n  \u003csummary\u003eExample\u003c/summary\u003e\n\nImagine you have a tool that creates invoices. There are two ways to generate an invoice: PDF and text. Every time the implementation for either PDF or text changes, the client needs to be adjusted. To solve this   problem, an InvoiceFactory is created. From this InvoiceFactory superclass, two concrete factories are derived: PdfFactory and TextFactory. These classes are responsible for creating the objects, so the client doesn't need to know about the specific implementations. The client interacts just with the factories. \n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eCode Example\u003c/summary\u003e\n\n  ```python\n\n  from abc import ABC, abstractmethod\n\n  class Invoice(ABC):\n      @abstractmethod\n      def print_invoice(self):\n          pass\n  \n  class PDFInvoice(Invoice):\n      def print_invoice(self):\n          print(\"PDF invoice is created and saved.\")\n  \n  class TextInvoice(Invoice):\n      def print_invoice(self):\n          print(\"Text invoice is created and printed.\")\n  \n  class InvoiceTool(ABC):\n      @abstractmethod\n      def create_invoice(self):\n          pass\n  \n      def generate_invoice(self):\n          invoice = self.create_invoice()\n          invoice.print_invoice()\n  \n  class PDFInvoiceTool(InvoiceTool):\n      def create_invoice(self):\n          return PDFInvoice()\n  \n  class TextInvoiceTool(InvoiceTool):\n      def create_invoice(self):\n          return TextInvoice()\n  \n  def main():\n      tool = PDFInvoiceTool()\n      tool.generate_invoice()\n      tool = TextInvoiceTool()\n      tool.generate_invoice()\n\n\n  ```\n\u003c/details\u003e\n\n\n# Contribution \n\nIf you'd like to contribute, feel free to create a pull request. \nIf you think a topic is missing, please open an issue.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjanmarkuslanger%2Fsoftware-architecture-guide","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjanmarkuslanger%2Fsoftware-architecture-guide","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjanmarkuslanger%2Fsoftware-architecture-guide/lists"}