Skip to content

Commit

Permalink
Added MultiTenancy. #4
Browse files Browse the repository at this point in the history
  • Loading branch information
jezzsantos committed Mar 10, 2024
1 parent fb517d0 commit 378c64a
Show file tree
Hide file tree
Showing 209 changed files with 7,456 additions and 1,127 deletions.
172 changes: 172 additions & 0 deletions docs/design-principles/0025-modularity.md

Large diffs are not rendered by default.

48 changes: 31 additions & 17 deletions docs/design-principles/0040-configuration.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,60 @@
# Configuration

Every SaaS system is driven by configuration.

In order to run these systems is different environments (e.g., `Local`, `Staging` and `Production`), different configuration is required, so that we can protect ourselves when developing a running system.

## Design Principles

1. We want all components in each host to have access to static configuration (read-only), that would be set at deployment time.
2. We want that configuration to be written into packaged assets just before deployment, in the CD pipeline.
3. We want that configuration to be specific to a specific environment (e.g. Local development, Staging, Holding or Production)
4. We do not want developers writing anything but local development environment settings (secrets or otherwise) into configuration files. With one exception: the configuration used to configure components for integration testing against real 3rd party systems (i.e. in tests of the category: `Integration.External`). These 3rd party accounts/environments, should never be related to production environments, and are designed only for testing-only. Configuration (and especially any secrets) used for these accounts/environments can NEVER lead those with access to them to compromise the system or its integrity.
5. We will need some configuration for the SaaS "platform" (all shared components), and some configuration for each "tenant" running on the platform. These two sets of configuration must be kept separate for each other, but may not be stored in the same repositories. (e.g. platform configuration is defined in appsettings.json, whilst tenancy configuration is stored in a database)
6. Configuration needs to be hierarchical (e.g. namespaced), and hierarchical in terms of layering.
7. Settings are expected to be of only 3 types: `string`, `number` and `boolean`
1. We want all components in each runtime host (e.g., `ApiHost1`) to have access to static configuration (read-only), that would be set at deployment time. (i.e., in static files like `appsettings.json`)
2. We want that configuration to be over-written into static packaged assets (like `appsettings.json`) just before deployment, in the CD pipeline. Rather then as design time by a developer, so we avoid storing these settings in source code, or expose them to unprivileged people designing the system.
3. We want that configuration to be specific to a specific environment (e.g. `Local` development, `Staging`, or `Production`)
4. We do not want developers writing anything but `local` development environment settings (secrets or otherwise) into configuration files. With one exception, see note below.
5. We will need some "shared" configuration for the SaaS "platform" (used by all components), and some "private" configuration used by each "tenant" running on the platform. These two sets of configuration must be kept separate from each other, and may not be stored in the same repositories. (e.g. platform configuration is defined in `appsettings.json`, whilst tenancy configuration is stored in a data stores)
6. Configuration needs to be hierarchical (e.g. it can be grouped by namespace), and hierarchical in terms of layering.
7. Settings are expected to be of only 3 kinds: `string`, `number` and `boolean`
8. Components are responsible for reading their own configuration, and shall not re-use other components configuration.
9. Secrets may be stored separately from non-confidential configuration in other repositories (e.g. files, databases, 3rd party services).
10. We want to be able to change storage location of configuration at any time, without breaking code (e.g. files, databases, 3rd party services).
11. We want to use dependency injection to give components their configuration.

> Point 4 above, with one exception: the configuration used to configure components for integration testing against real 3rd party systems (i.e. in tests of the category: `Integration.External`). These 3rd party accounts/environments, should never be related to production environments, and are designed only for testing-only. Configuration (and especially any secrets) used for these accounts/environments can NEVER lead those with access to them to compromise the system or its integrity.
## Implementation

The `IConfigurationSettings` abstraction is used to give access to configuration for both "Platform" settings and "Tenancy" settings.
The `IConfigurationSettings` abstraction is used to give access to configuration for both `Platform` settings and `Tenancy` settings.

It is injected into any adapters that require access to any configuration settings.

In order to operate effectively at runtime, the selection mechanism of whether to use a `Tenancy` setting or whether to use a `Platform` setting must be dynamic at runtime and must be dependent on whether the actual inbound HTTP request is destined for a specific tenant or not.

For these reasons, the `AspNetDynamicConfigurationSettings` adapter is to be used.

> This adapter is injected as both a "singleton" and a "scoped" in the DI container, depending on what adapters need which version of it.
### Platform Settings

Platform settings are setting that are shared across all components running in the platform.
Platform settings are "shared" across all components running in the platform, regardless of the lifetime of the dependency.

For example:

* Connection strings to centralized repositories (for hosting data pertaining to all tenants on the platform)
* Account details for accessing shared 3rd party system accounts via adapters (e.g. an email provider)
* Account details for accessing shared 3rd party system accounts via adapters (e.g., an email provider)
* Keys and defaults for various application and domain services

Most of these settings will be stored in standard places that are supported by the .NET runtime, such as `appsettings.json` files for the specific environment.
> Most of these settings will be stored in standard places that are supported by the .NET runtime, such as `appsettings.json` files for the specific environment.
### Tenancy Settings

Tenancy settings are setting that are specific to a tenant running on the platform.
Tenancy settings are "private" and are specific to a tenant running on the platform, they are only applicable to "scoped" dependencies.

For example:

* Connection strings to a tenant's physically partitioned repository (e.g. in a nearby datacenter of their choice)
* Account details for accessing a specific 3rd party system account via adapters (e.g. an accounting integration)
* Connection strings to a tenant's physically partitioned repository (e.g., in a nearby data center of their choice)
* Account details for accessing a specific 3rd party system account via adapters (e.g., an accounting integration)

At runtime, in a multi-tenanted host, when the inbound HTTP request is destined for an API that is tenanted, the `ITenantContext` will define the tenancy and settings for the current HTTP request.
At runtime, in a multi-tenanted host, when the inbound HTTP request is destined for an API that is tenanted, the `ITenantContext` will define the tenancy and it will define the `ITenancyContext.Settings` for the current HTTP request.

These settings are generally read from a dynamic repository (e.g. a database, or 3rd party service), and they are unique to the specific tenant.
These settings are read from the `IOrganizationsRepository` (i.e., a data store), and can be updated by other APIs.

> Never to be accidentally accessed by or exposed to other tenants running on the platform
> These settings are never to be accidentally accessed by or exposed to other tenants running on the platform.
4 changes: 3 additions & 1 deletion docs/design-principles/0110-back-end-for-front-end.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ A web BEFFE is a web server designed specifically to serve a web application (i.

A BEFFE is a de-coupling design strategy to avoid designing a Backend API that is directly coupled to a specific Frontend, or a Backend API that has to be all things to several Frontend applications (e.g., a web application, an admin web application, a mobile application, and a desktop application), all of which have different needs for data processing.

> This frees the designer of the Backend to focus on designing REST APIs instead of RPC/CRUD APIs to only serve a single Frontend.
> This frees the developer/designer of the Backend to focus on designing REST APIs instead of RPC/CRUD APIs to only serve a single dedicated Frontend.
In many contexts of evolving SaaS products, a BEFFE can act as an [Anti-Corruption Layer (ACL)](https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer) whenever the Backend needs to be re-designed/re-engineered for legacy reasons, as a business grows and starts taking onboard direct integrations with the backend. Or when the product evolves to be more of an integration platform.

## Design Principles

Expand Down
Loading

0 comments on commit 378c64a

Please sign in to comment.