diff --git a/README.md b/README.md index ebeaa50e..84554072 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Backend API Ready](https://img.shields.io/badge/Status-BackendAPI_Ready-green.svg)](README.md) +[![Backend API ReadyEnough](https://img.shields.io/badge/Status-BackendAPI_Ready_Enough-green.svg)](README.md) [![Frontend WebApp Under Construction](https://img.shields.io/badge/Status-FrontendUI_UnderConstruction-yellow.svg)](README.md) [![Build and Test](https://github.com/jezzsantos/saastack/actions/workflows/build.yml/badge.svg)](https://github.com/jezzsantos/saastack/actions/workflows/build.yml) [![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/jezzsantos) @@ -13,20 +13,20 @@ It is a complete template for building real-world, fully featured SaaS web produ Ready to build, test, and deploy into a cloud provider of your choice (e.g., Azure, AWS, Google Cloud, etc.) -> Don't spend months building all this stuff from scratch. You and your team don't need to. We've done all that for you already; just take a look, see what is already there and take it from here. You can always change it the way you like it as you proceed, you are not locked into anyone else's framework. +> Don't spend months building all this stuff from scratch. You and your team don't need to. We've done all that for you already; just take a look, see what is already there, and take it from here. You can always change it the way you like it as you proceed. You are not locked into anyone else's framework. > > This is not some code sample like those you would download to learn a new technology or see in demos online. This is way more comprehensive, way more contextualized, and way more realistic about the complexities you are going to encounter in reality. > This template contains a partial (but fully functional) SaaS product that you can deploy from day one and start building your product on. But it is not yet complete. That next part is up to you. The codebase demonstrates common architectural styles that you are going to need in your product in the long run, such as: -* [A Modular-Monolith](https://www.thoughtworks.com/insights/blog/microservices/modular-monolith-better-way-build-software) - always build a monolith first, then separate out to micro-services later -* [Clean Architecture, Onion Architecture, and Hexagonal Architecture](https://medium.com/@edamtoft/onion-vs-clean-vs-hexagonal-architecture-9ad94a27da91) all the same principles - low-coupling, high-cohesion, a shareable and protected domain at the center +* [A Pluggable Modular-Monolith](https://www.thoughtworks.com/insights/blog/microservices/modular-monolith-better-way-build-software) - always build a monolith first, then separate out to micro-services later if you need to +* [Clean Architecture, Onion Architecture, and Hexagonal Architecture](https://medium.com/@edamtoft/onion-vs-clean-vs-hexagonal-architecture-9ad94a27da91) all have the same principles - low-coupling, high-cohesion, a shareable and protected domain at the center * Hosted behind a distributed REST API, or in a CLI, (or in another executable). * [Domain Driven Design](https://martinfowler.com/bliki/DomainDrivenDesign.html) (with Aggregates and Domain Events) - modeling actual real-world behaviors, not modeling just anemic data * [Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html) - because you cannot predict upfront when you will need historical data later, and when you do, will be stuck, also makes domain events a cinch -* [Event-Driven Architecture](https://learn.microsoft.com/en-us/azure/architecture/guide/architecture-styles/event-driven) - to keep your modules de-coupled and asynchronous from each other, on focused on meaningful events in your product -* [Polyglot Persistence](https://martinfowler.com/bliki/PolyglotPersistence.html) - decouples you from infrastructure, makes your entire system easy to test, and then upgrade as your business scales later +* [Event-Driven Architecture](https://learn.microsoft.com/en-us/azure/architecture/guide/architecture-styles/event-driven) - to keep your modules de-coupled, distributed, and asynchronous from each other, focused on meaningful events across your product +* [Polyglot Persistence](https://martinfowler.com/bliki/PolyglotPersistence.html) - decouples you from infrastructure, makes your entire system easy to test, and then upgrades as your business scales later * Extensive Test Automation (e.g., Unit, Integration, and E2E) so you can keep moving years into the future * B2B or B2C Multitenancy, you choose * Extensibility for all integrations with any 3rd party provider (e.g., Stripe, Twilio, LaunchDarkly, etc.) - because you want to start cheaply, and change over time as your new business changes and grows. @@ -38,20 +38,34 @@ This starter template gives you most of the things all SaaS products will need f ## What is in the box? ![Azure](docs/images/Physical-Architecture-Azure.png) + or if you prefer AWS: + ![AWS](docs/images/Physical-Architecture-AWS.png) +## How is the code structured? + +The best experience for working with this template is in an IDE like JetBrains Rider, or Visual Studio, or Visual Studio Code (opening the solution file). + +> However, if working in an IDE is not your team's thing, then you can also rearrange the project folders into whatever structure you like. It is a starter template after all. + +![Solution](docs/images/Logical-Structure.png) + ## Who is it for? -This starter template is NOT for everyone, nor for EVERY software project, nor for EVERY skill level. We need to say that because all software products are different, there is not one silver bullet for all of them. +This starter template is NOT for everyone, nor for EVERY software project, nor for EVERY skill level. + +> We need to say that because all software products are different, there is not one silver bullet for all of them. -* The people using this template must have some experience applying "first principles" of building new software products from scratch because it is a starter template that can (and should) be modified to suit your context. It is a far better starting point than building everything from scratch again. +* The people using this template must have some experience applying "first principles" of building new software products from scratch because it is a starter template that can (and should) be modified to suit your context. (It is a far better starting point than building everything from scratch again. You need to understand the principles, not have to rewrite them all over again!). -* The tech stack is a .NET core backend (LTS version 6.0 or later) written in C#, using a few very popular and well-supported 3rd party libraries +* The tech stack is a .NET core backend (LTS version 8.0 or later) written in C#, using (a few) but very popular and well-supported 3rd party libraries. (We've worked very hard to find a balance between too few and far too many). * This starter template deliberately makes engineering trade-offs that are optimized for situations where: - 1. High maintainability is super important to you (e.g., long-lived codebases) + 1. High maintainability is super important to you over long periods of time (e.g., long-lived codebases) 2. Managing complexity over long periods of time is non-negotiable (~1-10 years), and avoiding big balls of mud (BBOMs) is paramount to you, - 3. Where many hands will touch the codebase (i.e., over the course of its entire life) + 3. Where many hands will touch the codebase (i.e., over the course of its entire life). Of course, if you are working alone on a project, you will have personal preferences, free from the practical constraints of working in teams. + +## What is it for? The kinds of '*known scenarios*' that this template is designed specifically for: @@ -71,27 +85,31 @@ Are these trade-offs suitable for any kind of software project? ## What does it give you? -It is a starter "template," not a 3rd party library or a fancy 3rd party framework: +It is a starter "template," not a 3rd party library or a fancy 3rd party framework. Once you clone it it is all yours: * You copy this codebase, as is, as your new codebase for your product. * You rename a few things to the name of your product. * You compile it, you run its tests, and you deploy its pieces into your cloud environment (e.g., Azure, AWS, or Google Cloud). -* You then continue to evolve and add your own features to it (by following the established code patterns). You then evolve and adapt the code to wherever you need it to go. +* You then continue to evolve and add your own features to it (by following the established code patterns). +* You then evolve and adapt the code to wherever you need it to go. * Don't like those patterns? then change them to suit your preferences. There are no rigid frameworks or other dev teams to plead with. +* At some point, you will delete the example subdomain modules (Cars and Bookings) that are provided as examples to follow and, of course, replace them with your own subdomain modules. * Read the [documentation](docs/README.md) to figure out what it already has and how things work. * So that you either don't need to worry about those specific things yet (and can focus on more valuable things), or you can modify them to suit your specific needs. It is your code, so you do as you please to it. -Since this starter "template" is NOT a framework (of the type you usually depend on from others downloaded from [nuget.org](https://nuget.org)), you are free from being trapped inside other people's abstractions and regimes and then waiting on them to accommodate your specific needs. With this template, all you need to do is understand the code, change the code to fit your needs, update the tests that cover it, and move on. Just like you do with all the code you write. +Since this starter "template" is NOT a framework (of the type you usually depend on from others downloaded from [nuget.org](https://nuget.org)), you are free from being trapped inside other people's abstractions and regimes and then waiting on them to accommodate your specific needs. + +> With this template, all you need to do: is (1) understand the code here, (2) change the code to fit your needs, (3) update the tests that cover those changes, and (4) move on. Just like you do with any and all the code you write when you join a new company, team or project. It is no different to that. ## Want it to scale? -What happens when the performance of this modular monolith requires that you must scale it out? +What happens when the performance of this modular monolith requires that you MUST scale it out, and break it into independently deployable pieces? > Remember: No business can afford the expense for you to re-write your product, - so forget that idea! This codebase has been explicitly designed so that you can split it up and deploy its various modules into separate deployable units as you see fit (when your product is ready for that). -Unlike a traditional monolithic codebase (i.e., single deployable unit), all modules in this Modular Monolith codebase have been designed (and enforced) to be de-coupled and deployed independently in the future. +Unlike a traditional monolithic codebase (i.e., a single deployable unit), all modules in this Modular Monolith codebase have been designed (and enforced) to be de-coupled and deployed independently in the future. You just have to decide which modules belong in which deployed components, wire things up correctly (in the DI), and you can deploy them separately. @@ -134,6 +152,7 @@ The starter template also takes care of these specific kinds of things: * Applications are aligned to audiences and subdomains * Others * It provides documented code examples for the most common use cases. Simply follow and learn from the existing patterns in the codebase + * It provides [how-to guides](docs/how-to-guides/README.md) for performing the most common things on a codebase like this, until you've learned the patterns. * It provides a [decision log](docs/decisions/README.md) so you can see why certain design decisions were made. * It provides documentation about the [design principles](docs/design-principles/README.md) behind the codebase so you can learn about them and why they exist. * It \[will\] provide an eco-system/marketplace of common adapters that other people can build and share with the community. diff --git a/docs/design-principles/0170-eventing.md b/docs/design-principles/0170-eventing.md index c53ef549..09a8c28a 100644 --- a/docs/design-principles/0170-eventing.md +++ b/docs/design-principles/0170-eventing.md @@ -6,21 +6,24 @@ Eventing is the gateway to Event Driven Architecture (EDA), but it starts in the 1. We want all DDD aggregates to utilize "domain events" to drive use cases, irrespective of whether we are sourcing/capturing their current state from events (a.k.a Event Sourcing) or sourcing/capturing their current state from data snapshots (i.e. traditional record persistence). 2. For [event-sourcing persistence schemes](0070-persistence.md) (that are by definition "write models" only), we need to build associated "read models" so that we can query domain aggregates. -3. We want the flexibility to change our "read models" at any time, as the software changes, and ideally not have lost any data. -4. We may want denormalized data models to query for maximum efficiency. +3. We want the flexibility to add/change our "read models" at any time, as the software changes, and not have lost any data, and ideally re-create views of the data not previously seen (not persisted) in the past. +4. We want to use denormalized views of data to query for maximum efficiency (i.e. without complex joins). 5. We want to de-couple subdomains from each other as much as possible. Even for subdomains that are highly-coupled to begin with (e.g., `EndUsers` and `Organizations` and `Subscriptions`. -6. We want to deploy certain groups of subdomains into separate hosts and split the modular monolith into many APIs (i.e., micros-services) later in the lifecycle of the product, but not have to re-engineer flows to accommodate those changes. -7. We want the flexibility to make changes to key use cases in the product, without changing multiple subdomains at the same time. -8. We want to be able to communicate across process boundaries without coupling the processes. +6. We want to deploy certain groups of subdomains into separate hosts and split the modular monolith into many independent hosts (i.e., micros-services) later in the lifecycle of the product, but not have to re-engineer flows to accommodate those changes. +7. Sending imperative "commands" (i.e. API calls) between different API's is by its nature unreliable, and it encourages coupling (and assumptions) in many subtle ways for the software designer. We want to try to minimize that. +8. We want the flexibility to make changes to key use cases in the product, without changing multiple subdomains at the same time. +9. We want to be able to communicate across process boundaries without coupling the processes. ## Implementation In the design of most distributed systems, of the nature of this system (or of systems that are expected to evolve into distributed systems later) it is common practice to decouple each of the subdomains from each other. De-coupling effectively is absolutely vital to allowing the system to change independently, grow, and evolve over time. -> This is the whole point of having a single direction of dependencies, from: Infrastructure components -> Domain components. +> Decoupling is the whole point of having a single direction of dependencies, from: Infrastructure components -> Domain components. Coupling is the primary cause of the "accidental complexity" that software designers put into their code, in small incremental steps. Lack of effective de-coupling (at the technical level) is the main reason most software systems devolve into big-balls-of-mud, simply because of the coupling of many components to many other components often striving for maximum data and code reuse. +> Code re-use is a useful technique to combat the problem of not having to update multiple places in the code/system that share the same precise behaviour/context. But this technique (and blind pursuit of it) is also the primary cause of coupling when components, that have distinct behaviour, are changed and their behaviour/context diverges. Knowing the difference is an important skill to develop. Being DRY is not the goal, it is a precision tool that needs to be applied carefully. + There are several techniques for de-coupling your subdomains, including: separating layers, using ports and adapters, starting with a modular monoliths and decomposing it into microservices later etc. Another one of these techniques is the use of Event-Driven Architecture (EDA), where change is communicated within process boundaries, and across process boundaries. diff --git a/docs/images/Logical-Structure.png b/docs/images/Logical-Structure.png new file mode 100644 index 00000000..e0a1bbd8 Binary files /dev/null and b/docs/images/Logical-Structure.png differ diff --git a/docs/images/Sources.pptx b/docs/images/Sources.pptx index e8d22c95..c63178da 100644 Binary files a/docs/images/Sources.pptx and b/docs/images/Sources.pptx differ