Skip to content

Commit

Permalink
Tidying, grammar, and minor clarifications (#13)
Browse files Browse the repository at this point in the history
* Tidying, grammar, and minor clarifications

* Changelog for 1.2.1

* Update README for 1.2.1
  • Loading branch information
phalt authored Sep 25, 2019
1 parent 829ffd3 commit 023eeff
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 37 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
1.2.1 | 25-09-2019
================

- Fixed a lot of grammar issues.
- Fixed some styling stuff to make things easier to read.
- A note on shall methods / pass-through methods code-smell.
- A note on wording.

Contributors:

- Paul Hallett (Author)

1.2 | 10-06-2019
================

Expand Down Expand Up @@ -31,4 +43,4 @@ Initial version

Contributors:

- Paul Hallett (Author)
- Paul Hallett (Author)
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
# Django API Domains
_Style guides for the API age_

| Version | Author(s) | Date |
| ------------------------------------------------------------------- |-------------------------------------------|------------|
| [1.2](https://github.com/phalt/django-api-domains/releases/tag/1.2) | Paul Hallett [email protected] | 10-06-2019 |
| [1.1](https://github.com/phalt/django-api-domains/releases/tag/1.1) | Paul Hallett [email protected] | 09-04-2019 |
| [1.0](https://github.com/phalt/django-api-domains/releases/tag/1.0) | Paul Hallett [email protected] | 01-02-2019 |
| Version | Author(s) | Date |
| ----------------------------------------------------------------------- |-------------------------------------------|------------|
| [1.2.1](https://github.com/phalt/django-api-domains/releases/tag/1.2.1) | Paul Hallett [email protected] | 25-09-2019 |
| [1.2](https://github.com/phalt/django-api-domains/releases/tag/1.2) | Paul Hallett [email protected] | 10-06-2019 |
| [1.1](https://github.com/phalt/django-api-domains/releases/tag/1.1) | Paul Hallett [email protected] | 09-04-2019 |
| [1.0](https://github.com/phalt/django-api-domains/releases/tag/1.0) | Paul Hallett [email protected] | 01-02-2019 |

## Introduction

Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ In order to overcome these problems, this styleguide tries to achieve the follow

## Current Version

**CURRENT VERSION: [VERSION 1.2](https://github.com/phalt/django-api-domains/releases/tag/1.2)**
**CURRENT VERSION: [VERSION 1.2.1](https://github.com/phalt/django-api-domains/releases/tag/1.2.1)**

## Previous Versions

All previous versions can be found under the `docs/` folder when looking at a [specific tag](https://github.com/phalt/django-api-domains/releases).

## CHANGELOG

Please see [CHANGELOG.md](https://github.com/phalt/django-api-domains/blob/master/CHANGELOG.md)
Please see [CHANGELOG.md](https://github.com/phalt/django-api-domains/blob/master/CHANGELOG.md)
33 changes: 24 additions & 9 deletions docs/domains.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
# Domains

A [domain](https://en.wikipedia.org/wiki/Domain_(software_engineering)) is a piece of software that provides distinct business value within the context of your application.
A [domain](https://en.wikipedia.org/wiki/Domain_(software_engineering)) is a piece of software that provides a distinct **business** value for your application.

Within the context of software, what this styleguide calls a `domain` is roughly an extension of what Django would call an `app`. Therefore a _business_ domain **should** have at least one distinct _software_ domain mirroring it.
What this styleguide calls a `domain` is roughly an extension of what Django would call an `app`. Therefore a business domain **should** have at least one distinct software domain mirroring it.

## Examples in this guide
---

The examples in this guide will talk about a `book shop` that must share details about books. This can be modelled as a _domain_ called `books`, and as a _software domain_ also called `books`.
> ### Examples in this guide
We keep the key benefits of Django's `app` pattern - namely Django's [models](https://docs.djangoproject.com/en/2.1/topics/db/models/) to represent tables in a datastore, with an emphasis on **skinny models**. We also retain Django's ability to *package apps as installable components in other applications*. This allows domains to be easily migrated to different codebases or completely different projects.
> The examples in this guide will talk about a `book shop` that shares details about books.
> This can be modelled as a _domain_ called `books`, and as a _software domain_ also called `books`.
---

This guide tries to keep the key benefits of Django's `app` pattern - namely Django's [models](https://docs.djangoproject.com/en/2.1/topics/db/models/) to represent tables in a datastore, but with an emphasis on **skinny models**.

This guide also retain Django's ability to *package apps as installable components in other applications*. This allows domains to be easily migrated to different codebases or completely different projects.

## Domain rules

There are two high-level rules around domains:
There are two major rules around domains:

1. You **should** split a domain if it becomes too big to work on.

As a rule of thumb is that a domain should allow between 4-6 developers (3 pairs) to comfortably work on it. If you find your developers being blocked by each other then it is time to consider splitting the domain or checking the software has not diverged too far from the styleguide.
A domain should allow between 4-6 developers (3 pairs) to comfortably work on it. If you find your developers being blocked by each other then it is time to consider splitting the domain or checking the software has not diverged too far from the styleguide.

---

2. You **should** adhere to the styleguide patterns in this document in order to maintain strong bounded contexts between your domains.

This applies even in situations where you extract one domain into two domains to increase velocity, but they still have to maintain a dependency between each other. We have found that if you relax the bounded context between domains, the boundary will erode and you will lose the ability to work on them independent of each other.
This applies even in situations where you extract one domain into two domains to increase velocity, but they still have to maintain a dependency between each other. We have found that if you relax the bounded context between domains, the boundary will erode and you will lose the ability to work on them independent of each other.


---

> ### Protip
> An example _software_ domain is provided in the same directory as this styleguide under [example_domain/](https://github.com/phalt/django-api-domains/tree/master/example_domain).
An example _software_ domain is provided in the same directory as this styleguide under [example_domain/](https://github.com/phalt/django-api-domains/tree/master/example_domain).
---

Next, we will discuss the styleguide in detail.
15 changes: 11 additions & 4 deletions docs/files.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@

### Examples on this page

In the examples below we imagine a service with two domains - one for books, and one for authors. The abstraction between books and authors is only present to demonstrate the concepts in the styleguide. You could argue that Books and Authors can live in one domain. In our example **we also assume a book can only have one author.** It's a strange world.

## Models

Models defines how a data model/ database table looks. This is a Django convention that remains mostly unchanged. The key difference here is that you use _skinny models_ - no complex functional logic should live here. In the past Django has recommended an [active record](https://docs.djangoproject.com/en/2.1/misc/design-philosophies/#models) style for it's models. In practice, we have found that this encourages developers to make `models.py` bloated and do too much - often binding the presentation and functional logic of a domain too tightly. This makes it very hard to have abstract presentations of the data in a domain. Putting all the logic in one place also makes it difficult to scale the number of developers working in this part of the codebase. See the _"Where should logic live?"_ section above for clarification.
Models defines how a data model/ database table looks. This is a Django convention that remains mostly unchanged. The key difference here is that you use _skinny models_. No complex functional logic should live here.

In the past Django has recommended an [active record](https://docs.djangoproject.com/en/2.1/misc/design-philosophies/#models) style for it's models. In practice, we have found that this encourages developers to make `models.py` bloated and do too much - often binding the presentation and functional logic of a domain too tightly. This makes it very hard to have abstract presentations of the data in a domain. Putting all the logic in one place also makes it difficult to scale the number of developers working in this part of the codebase. See the [_"Which logic lives where?"_](/styleguide/#which-logic-lives-where) section for clarification.

A models.py file can look like:

Expand All @@ -29,12 +33,13 @@ class Book(models.Model):
- Models **should** own informational logic related to them.
- Models **can** have computed properties where it makes sense.
- Models **must not** import services, interfaces, or apis from their own domain or other domains.
- Table dependencies (such as ForeignKeys) **must not** exist across domains. Use a UUID field instead, and have your Services control the relationship between models. You **can** use ForeignKeys between tables in one domain. Be aware that this might hinder future refactoring.
- Table dependencies (such as ForeignKeys) **must not** exist across domains. Use a UUID field instead, and have your Services control the relationship between models.
- You **can** use ForeignKeys between tables in one domain. (But be aware that this might hinder future refactoring.)


## APIs

APIs defines the External API interface for your domain. Anyone using the APIs defined here is called a _consumer_. The API can be either an HTTP API using [graphQL](https://github.com/graphql-python) or [REST](https://www.django-rest-framework.org/) for consumers over the web, or a software API for internal consumers. APIs is defined in `apis.py` which is agnostic to the implementation you chose, and you can even put more than one API in a domain. For example - you might want to wrap a graphQL API _and_ a REST API around your domain for different consumers.
APIs defines the external API interface for your domain. Anyone using the APIs defined here is called a _consumer_. The API can be either an HTTP API using [GraphQL](https://github.com/graphql-python) or [REST](https://www.django-rest-framework.org/) for consumers over the web, or a software API for internal consumers. APIs is defined in `apis.py` which is agnostic to the implementation you choose, and you can even put more than one API in a domain. For example - you might want to wrap a GraphQL API _and_ a REST API around your domain for different consumers.

An apis.py file that defines a simple software API can look like:

Expand Down Expand Up @@ -64,7 +69,7 @@ class BookAPI:
- If you are using a class for your internal APIs, it **must** use the naming convention `MyDomainAPI`.
- Internal functions in APIs **must** use type annotations.
- Internal functions in APIs **must** use keyword arguments.
- You **should** log API call functions.
- You **should** log API function calls.
- All data returned from APIs **must be** serializable.
- APIs **must** talk to Services to get data.
- APIs **must not** talk to Models directly.
Expand All @@ -78,6 +83,8 @@ Your domain may need to communicate with another domain. That domain can be in a

Consider interfaces.py like a mini _Anti-Corruption Layer_. Most of the time it won't change and it'll just pass on arguments to an API function. But when the other domain moves - say you extract it into it's own web service, your domain only needs to update the code in `interfaces.py` to reflect the change. No complex refactoring needed, woohoo!

It's worth noting that some guides would consider this implementation a code smell because it has the potential for creating shallow methods or pass-through methods. This is somewhat true, and leads us back to the [pragmatism](/using/#be-pragmatic) point in our guide. If you find your `interfaces.py` is redundant, then you probably don't need it. That said: **we recommend starting with it and removing it later**.

An interfaces.py may look like:

```python
Expand Down
68 changes: 57 additions & 11 deletions docs/styleguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@
* A domain **must** use the following file structure:

```
apis.py - Public functions and access points, presentation logic.
interfaces.py - Integrations with other domains or external services.
models.py - Object models and storage, simple information logic.
services.py - coordination and transactional logic.
- apis.py - Public functions and access points, presentation logic.
- interfaces.py - Integrations with other domains or external services.
- models.py - Object models and storage, simple information logic.
- services.py - coordination and transactional logic.
```

In addition, any existing files from a standard Django app are still allowed, such as `urls.py`, `apps.py` and `migrations/*`. `Views.py` in [Django's pattern](https://docs.djangoproject.com/en/dev/#the-view-layer) is **explicitly not allowed** in this styleguide pattern as we only focus on API-based applications. Most logic that used to live in Django's `views.py` would now be separated into `apis.py` and `services.py`.
In addition, any existing files from a standard Django app are still allowed, such as `urls.py`, `apps.py` and `migrations/*`.

* You **can** mask one of the required files as a directory for better file organisation. For example, you might want to split `apis.py` into this file structure:
* `Views.py` in [Django's pattern](https://docs.djangoproject.com/en/dev/#the-view-layer) is **explicitly not allowed** in this styleguide.

We only focus on API-based applications. Most logic that used to live in Django's `views.py` would now be separated into APIs and Services.

* You **can** mask one of the required files as a directory for better file organisation. For example, you might want to split `apis.py` file into this structure:

```
apis/
Expand All @@ -41,7 +45,7 @@ from domain.apis import Foo

This keeps namespaces tidy and does not leak domain details.

* A domain **does not** need to have all these files if it is not using them. For example - a domain that just coordinates API calls to other domains does not need to have `models.py` as it is probably not storing anything in a datastore.
* A domain **does not** need to have all these files if it is not using them. For example - a domain that just coordinates API calls to other domains does not need to have Models as it is probably not storing anything in a datastore.

* A domain **can have** additional files when it makes sense (such as `utils.py` or `enums.py` or `serializers.py`) to separate out parts of the code that aren't covered by the styleguide pattern.

Expand All @@ -62,8 +66,50 @@ With this ruling domains are easy to package and move around. When it comes time

## Which logic lives where?

It's common in programming to end up confused about what type of logic should live where - should it go in the `apis.py`, the `models.py`, or the `services.py`? There are many cases where it's difficult to decide, and the best advice is to **pick a pattern and stick to it**, but for simpler things, this guide emphasises the following:
It's common in programming to end up confused about what type of logic should live where.

There are many cases where it's difficult to decide, and the best advice is to **pick a pattern and stick to it**, but for simpler things, this guide emphasises the following:

### APIs
Logic about presentation.

---

> **If you ask:**
> "Where should I show this data to the user?" or "Where do I define the API schema?"
---


### Services
Logic around coordination and transactions.

---

> **If you ask:**
> "Where do I coordinate updating many models in one domain?" or "Where do I dispatch a single action out to other domains?"
---


### Models
Logic around information.

---

> **If you ask:**
> "Where can I store this data?" or "Where can I do any post/pre-save actions?"
---


### Interfaces

Logic handling the transformation of data from other domains.

---

> **If you ask:**
> "Where shall I connect to another domain?" or "How do I change the data format for another domain?"
- `apis.py` - logic about presentation (Where should I show this data to the user? Where do I define the API schema?)
- `services.py` - logic around coordination and transactions (Where do I coordinate updating many models in one domain? Where do I dispatch a single action out to other domains?)
- `models.py` - logic around information (Where can I store this data? Where can I do any post/pre-save actions?) and derived information computed by already existing information
---
17 changes: 12 additions & 5 deletions docs/using.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
# Pin against a version

In software we often specify a version of a project to use.
In software we often specify a version of a project to use. For example - `requests==1.2` - this says _"we want to use requests version 1.2"_.

For example - `requests==1.2` - this says _"use requests version 1.2"_.

For this guide, we recommend the following picking a specific version and working against it. Just like software; future versions of this guide could change and that would mean conflicting issues with the styleguide in your projects.
For this guide we recommend the following: picking a specific version and working against it. Just like software; future versions of this guide could change and that could cause conflicting issues with the styleguide in your project.

You can see the released versions [here](https://github.com/phalt/django-api-domains/releases) and by clicking on the tag icon you can view historical versions. The published version at [https://phalt.github.io/django-api-domains](https://phalt.github.io/django-api-domains) will always be the latest version.

# Be pragmatic

Don't blindly follow what other people have written down. Sometimes it might not be suitable for your own problem. Use this guide as inspiration and guidance, but make your own decisions with your team when you aren't satisfied with it.

Just make sure you keep track of any additional rules you create.
---

> ### Protip
> Keep track of any additional rules you make to keep your styleguide consistent!
---

# Wording in this guide

Sometimes this guide refers to the following areas in a domain: `interfaces`, `apis`, `services`, and `models`. You can assume any code examples are from the file name of the same area. E.g. - code examples under an `apis` heading is meant for the `apis.py` file.

0 comments on commit 023eeff

Please sign in to comment.