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: 5 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 (about 7 years ago)
- Default Branch: master
- Last Pushed: 2023-02-15T18:49:10.000Z (about 3 years ago)
- Last Synced: 2025-08-16T23:53:09.903Z (7 months ago)
- Topics: microservice, python
- Language: Python
- Size: 3.15 MB
- Stars: 3
- Watchers: 3
- 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 EventFactory
class BaseAuthorizer(EventFactory):
"""Minimal Authorizer Class."""
def __init__(self, access_list):
self.access_list = access_list
def 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 AuthToken
class Authorizer(BaseAuthorizer):
def __init__(self, access_list):
self.access_list = access_list
def 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.