Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/cosphere-org/lily
DDD inspired microservice framework based on Django and DRF
https://github.com/cosphere-org/lily
microservice python
Last synced: 4 months ago
JSON representation
DDD inspired microservice framework based on Django and DRF
- Host: GitHub
- URL: https://github.com/cosphere-org/lily
- Owner: cosphere-org
- Created: 2019-03-15T09:28:05.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2023-02-15T18:49:10.000Z (almost 2 years ago)
- Last Synced: 2024-10-01T06:21:01.830Z (4 months ago)
- Topics: microservice, python
- Language: Python
- Size: 3.15 MB
- Stars: 4
- Watchers: 4
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# WARNING: this project is still undergoing some heavy changes and is still quite poorly documented so if you're interested in using it, well do that at your own risk.
# Lily - microservices by humans for humans
Lily is built around:
- DDD (Domain Driven Design) = Commands + Events
- TDD+ (Test Driven Development / Documentation)## Foundations
Lily was inspired by various existing tools and methodologies. In order to understand the philosophy of `Lily` one must udnerstand two basic concepts:
- `COMMAND` - is a thing one can perform
- `EVENT` - is a consequence of executing `COMMAND` (one `COMMAND` can lead to many events).In `lily` we define commands that are raising (python's `raise`) various events that are captured by the main events loop (do not confuse with node.js event loop).
## Creating HTTP commands
`Lily` enable very simple and semantic creation of commands using various transport mechanism (HTTP, Websockets, Async) in a one unified way.
Each HTTP command is build around the same skeleton:
```python
from lily import (
command,
Meta,
name,
Input,
Output,
serializers,
Access,
HTTPCommands,
)class SampleCommands(HTTPCommands):
@command(
name=,meta=Meta(
title=,
description=,
domain=),access=Access(access_list=),
input=Input(body_parser=),
output=Output(serializer=),
)
def (self, request):raise self.event.({'some': 'thing'})
```The simplest are HTTP commands that can be defined in the following way:
```python
from lily import (
command,
Meta,
name,
Input,
Output,
serializers,
Access,
HTTPCommands,
)class SampleCommands(HTTPCommands):
@command(
name=name.Read(CatalogueItem),meta=Meta(
title='Bulk Read Catalogue Items',
domain=CATALOGUE),access=Access(access_list=['ADMIN']),
input=Input(body_parser=CatalogueItemParser),
output=Output(serializer=serializers.EmptySerializer),
)
def get(self, request):raise self.event.Read({'some': 'thing'})
```### Names
FIXME: add it ...## Creating Authorizer class
Each `command` created in Lily can be protected from viewers who should not be
able to access it. Currently one can pass to the `@command` decorator
`access_list` which is passed to the `Authorizer` class.```python
from lily.base.events import EventFactoryclass BaseAuthorizer(EventFactory):
"""Minimal Authorizer Class."""def __init__(self, access_list):
self.access_list = access_listdef authorize(self, request):
try:
return {
'user_id': request.META['HTTP_X_CS_USER_ID'],
'account_type': request.META['HTTP_X_CS_ACCOUNT_TYPE'],
}except KeyError:
raise self.AccessDenied('ACCESS_DENIED', context=request)def log(self, authorize_data):
return authorize_data```
But naturally it can take any form you wish. For example:
- it could expect `Authorization` header and perform `Bearer` token decoding
- it could leverage the existence of `access_list` allowing one to apply some
sophisticated `authorization` policy.An example of fairly classical (jwt token based `Authorizer` would be):
```python
from lily import BaseAuthorizer
from .token import AuthTokenclass Authorizer(BaseAuthorizer):
def __init__(self, access_list):
self.access_list = access_listdef authorize(self, request):
try:
type_, token = request.META['HTTP_AUTHORIZATION'].split()except KeyError:
raise self.AuthError('COULD_NOT_FIND_AUTH_TOKEN')else:
if type_.lower().strip() != 'bearer':
raise self.AuthError('COULD_NOT_FIND_AUTH_TOKEN')account = AuthToken.decode(token)
if account.type not in self.access_list:
raise self.AccessDenied('ACCESS_DENIED')# -- return the enrichment that should be available as
# -- `request.access` attribute
return {'account': account}def log(self, authorize_data):
return {
'account_id': authorize_data['account'].id
}```
Notice how above custom `Authorizer` class inherits from `BaseAuthorizer`.
In order to enable custom `Authorizer` class one must set in the `settings.py`:```python
LILY_AUTHORIZER_CLASS = 'account.authorizer.Authorizer'
```where naturally the module path would depend on a specific project set up.
Finally in order to use Authorization at the command level one must set in the @command definition:
```python
from lily import (
command,
Meta,
name,
Output,
serializers,
Access,
HTTPCommands,
)class SampleCommands(HTTPCommands):
@command(
name=name.Read(CatalogueItem),meta=Meta(
title='Bulk Read Catalogue Items',
domain=CATALOGUE),access=Access(access_list=['ADMIN']),
output=Output(serializer=serializers.EmptySerializer),
)
def get(self, request):raise self.event.Read({'some': 'thing'})
```where `access` entry explicitly specifies who can access a particular command, that list will be injected to the `Authorizer` on each request to the server.