Professional Documents
Culture Documents
Grading policy:
Component Weight
Labs 40
Course project and discussion 20
Final exam 40
Software freely available for this course:
• Flask: https://pypi.org/project/Flask
• SQLAlchemy: https://pypi.org/project/SQLAlchemy
ponyORM eStore
ponyORM PhotoSharing
ponyORM Biopsy
MANAGE COMPLEXITY.
FOR GROWTH.
Separate business logic from infrastructure concerns.
Structure the application to facilitate Pyramid Testing (unit,
integration, end-to-end).
Need efforts.
Layered design/archictecture (presentation layer – business
Copyright © 2021, 2022 Lan Hui 10
logic – database layer). See Figure 5-2 Typical Application
Layers. Do not cross layers. Like our government structure?
• Complexity.
• Changing requirements.
• Regression.
• High coupling.
Copyright © 2021, 2022 Lan Hui 13
• High cost in maintaining tests.
• Slow speed while testing the UI layer or the real data layer.
params = d i c t ( q= ’ S a u s a g e s ’ , format= ’ j s o n ’ )
handle = u r l o p e n ( ’ h t t p : / / a p i . duckduckgo . com ’ + ’ ? ’ + u r l e n c o d e ( params )
raw text = handle . read ( ) . decode ( ’ u t f 8 ’ )
parsed = json . loads ( raw text )
r e s u l t s = parsed [ ’ RelatedTopics ’ ]
for r in r e s u l t s :
i f ’ Text ’ i n r :
p r i n t ( r [ ’ F i r s t U R L ’ ] + ’ = ’ + r [ ’ Text ’ ] )
A medium-level abstraction
import r e q u e s t s
r e s u l t s = parsed [ ’ RelatedTopics ’ ]
for r in r e s u l t s :
i f ’ Text ’ i n r :
p r i n t ( r [ ’ F i r s t U R L ’ ] + ’ = ’ + r [ ’ Text ’ ] )
A high-level abstraction
import duckduckpy
f o r r i n duckduckpy . q u e r y ( ’ S a u s a g e s ’ ) . r e l a t e d t o p i c s :
print ( r . f i r s t u r l , ’ = ’ , r . text )
subgraph c l u s t e r d o m a i n {
subgraph c l u s t e r 1 {
a l l o c a t e [ c o l o r=g r a y , s t y l e= f i l l e d ] ;
OutOfStock ;
OrderLine ;
Batch
l a b e l = ” model . py ” ;
}
l a b e l = ”Domain ” ;
}
subgraph c l u s t e r a d a p t e r {
subgraph c l u s t e r 2 {
AbstractRepository ;
SqlAlchemyRepository ;
Dependency graph/network.
• Aggregate.
• Domain Service.
Tips:
Copyright © 2021, 2022 Lan Hui 26
1. Ask the domain expert for glossary and concrete examples.
@ d a t a c l a s s ( f r o z e n=True ) #( 1 ) ( 2 )
class OrderLine :
orderid : str
sku : s t r
qty : int
c l a s s Batch :
def init ( s e l f , r e f : str , sku : str , qty : int , eta : O p t i o n a l [ dat
#( 2 )
def a l l o c a t e ( s e l f , l i n e : O r d e r L i n e ) :
s e l f . a v a i l a b l e q u a n t i t y == l i n e . q t y #( 3 )
The EnglishPal story: the web UI was built after the domain
Copyright © 2021, 2022 Lan Hui 30
model had been built. In fact, I never thought I would build a
web application for EnglishPal.
- Freight partners.
- Manufacturers.
batch . a l l o c a t e ( l i n e )
def t e s t c a n a l l o c a t e i f a v a i l a b l e g r e a t e r t h a n r e q u i r e d ( ) :
l a r g e b a t c h , s m a l l l i n e = m a k e b a t c h a n d l i n e ( ”ELEGANT=LAMP” , 2 0 ,
assert large batch . can allocate ( small line )
def t e s t c a n n o t a l l o c a t e i f a v a i l a b l e s m a l l e r t h a n r e q u i r e d ( ) :
s m a l l b a t c h , l a r g e l i n e = m a k e b a t c h a n d l i n e ( ”ELEGANT=LAMP” , 2 , 2
assert small batch . can allocate ( l a r g e l i n e ) is False
def t e s t c a n n o t a l l o c a t e i f s k u s d o n o t m a t c h ( ) :
b a t c h = Batch ( ” batch = 001” , ”UNCOMFORTABLE=CHAIR” , 1 0 0 , e t a=None )
d i f f e r e n t s k u l i n e = O r d e r L i n e ( ” o r d e r = 123” , ”EXPENSIVE=TOASTER” , 1
a s s e r t batch . c a n a l l o c a t e ( d i f f e r e n t s k u l i n e ) i s F a l s e
• allocate
• can allocate
• deallocate
Copyright © 2021, 2022 Lan Hui 35
def t e s t c a n o n l y d e a l l o c a t e a l l o c a t e d l i n e s ( ) :
batch , u n a l l o c a t e d l i n e = m a k e b a t c h a n d l i n e ( ”DECORATIVE=TRINKET”
batch . d e a l l o c a t e ( u n a l l o c a t e d l i n e )
a s s e r t b a t c h . a v a i l a b l e q u a n t i t y == 20
Updated model:
c l a s s Batch :
def i n i t ( s e l f , r e f : str , sku : str , qty : int , eta : O p t i o n a l [ dat
self . reference = ref
s e l f . sku = sku
s e l f . eta = eta
s e l f . p u r c h a s e d q u a n t i t y = qty
s e l f . a l l o c a t i o n s = s e t ( ) # t y p e : S e t [ OrderLine ]
def a l l o c a t e ( s e l f , l i n e : O r d e r L i n e ) :
if self . can allocate ( line ):
s e l f . a l l o c a t i o n s . add ( l i n e )
@property
def a l l o c a t e d q u a n t i t y ( s e l f ) => i n t :
r e t u r n sum( l i n e . q t y f o r l i n e i n s e l f . a l l o c a t i o n s )
@property
def a v a i l a b l e q u a n t i t y ( s e l f ) => i n t :
return s e l f . p u r c h a s e d q u a n t i t y = s e l f . a l l o c a t e d q u a n t i t y
For something that has data but no ID. For example, order
line, name, and money.
Orer line:
@ d a t a c l a s s ( f r o z e n=True )
class OrderLine :
orderid : OrderReference
sku : ProductReference
qty : Quantity
@ d a t a c l a s s ( f r o z e n=True )
c l a s s Name :
first name : str
surname : s t r
c l a s s Money ( NamedTuple ) :
currency : str
value : int
def t e s t e q u a l i t y ( ) :
a s s e r t Money ( ’ gbp ’ , 1 0 ) == Money ( ’ gbp ’ , 1 0 )
def t e s t n a m e e q u a l i t y ( ) :
a s s e r t Name ( ” H a r r y ” , ” P e r c i v a l ” ) != Name ( ” B a r r y ” , ” P e r c i v a l ” )
def c a n a d d m o n e y v a l u e s f o r t h e s a m e c u r r e n c y ( ) :
a s s e r t f i v e r + f i v e r == t e n n e r
def c a n s u b t r a c t m o n e y v a l u e s ( ) :
a s s e r t t e n n e r = f i v e r == f i v e r
def a d d i n g d i f f e r e n t c u r r e n c i e s f a i l s ( ) :
def c a n m u l t i p l y m o n e y b y a n u m b e r ( ) :
a s s e r t f i v e r * 5 == Money ( ’ gbp ’ , 2 5 )
def m u l t i p l y i n g t w o m o n e y v a l u e s i s a n e r r o r ( ) :
with p y t e s t . r a i s e s ( TypeError ) :
tenner * f i v e r
def t e s t b a r r y i s h a r r y ( ) :
h a r r y = P e r s o n ( Name ( ” H a r r y ” , ” P e r c i v a l ” ) )
barry = harry
a s s e r t h a r r y i s b a r r y and b a r r y i s h a r r y
It is just a function.
Testing script:
def t e s t p r e f e r s c u r r e n t s t o c k b a t c h e s t o s h i p m e n t s ( ) :
i n s t o c k b a t c h = Batch ( ” i n = s t o c k = b a t c h ” , ”RETRO=CLOCK” , 1 0 0 , e t a=N
s h i p m e n t b a t c h = Batch ( ” s h i p m e n t = b a t c h ” , ”RETRO=CLOCK” , 1 0 0 , e t a=t
l i n e = O r d e r L i n e ( ” o r e f ” , ”RETRO=CLOCK” , 1 0 )
def t e s t p r e f e r s e a r l i e r b a t c h e s ( ) :
e a r l i e s t = Batch ( ” s p e e d y = b a t c h ” , ”MINIMALIST=SPOON” , 1 0 0 , e t a=t o d a
medium = Batch ( ” normal = b a t c h ” , ”MINIMALIST=SPOON” , 1 0 0 , e t a=tomorr
l a t e s t = Batch ( ” slow = b a t c h ” , ”MINIMALIST=SPOON” , 1 0 0 , e t a= l a t e r )
l i n e = O r d e r L i n e ( ” o r d e r 1 ” , ”MINIMALIST=SPOON” , 1 0 )
a l l o c a t e ( l i n e , [ medium , e a r l i e s t , l a t e s t ] )
a s s e r t e a r l i e s t . a v a i l a b l e q u a n t i t y == 90
a s s e r t medium . a v a i l a b l e q u a n t i t y == 100
a s s e r t l a t e s t . a v a i l a b l e q u a n t i t y == 100
Model:
def a l l o c a t e ( l i n e : O r d e r L i n e , b a t c h e s : L i s t [ Batch ] ) => s t r :
b a t c h = next ( b f o r b i n s o r t e d ( b a t c h e s ) i f b . c a n a l l o c a t e ( l i n e ) )
batch . a l l o c a t e ( l i n e )
return batch . r e f e r e n c e
Tips:
w i t h p y t e s t . r a i s e s ( OutOfStock , match=”SMALL=FORK” ) :
a l l o c a t e ( O r d e r L i n e ( ” o r d e r 2 ” , ”SMALL=FORK” , 1 ) , [ b a t c h ] )
Public contract.
Infrastructure ≈ Database
Usage example:
import a l l m y d a t a
def c r e a t e a b a t c h ( ) :
b a t c h = Batch ( . . . )
a l l m y d a t a . b a t c h e s . add ( b a t c h )
def m o d i f y a b a t c h ( b a t c h i d , n e w q u a n t i t y ) :
batch = a l l m y d a t a . batches . get ( b a t c h i d )
batch . c h a n g e i n i t i a l q u a n t i t y ( new quantity )
@abc . a b s t r a c t m e t h o d
def g e t ( s e l f , r e f e r e n c e ) => model . Batch :
ra is e NotImplementedError
What?
Need a database.
c l a s s O r d e r ( Base ) :
i d = Column ( I n t e g e r , p r i m a r y k e y=True )
c l a s s O r d e r L i n e ( Base ) :
i d = Column ( I n t e g e r , p r i m a r y k e y=True )
s k u = Column ( S t r i n g ( 2 5 0 ) )
qty = I n t e g e r ( S t r i n g (250))
o r d e r i d = Column ( I n t e g e r , F o r e i g n K e y ( ’ o r d e r . i d ’ ) )
o r d e r = r e l a t i o n s h i p ( Order )
c l a s s A l l o c a t i o n ( Base ) :
...
import model #( 1 )
o r d e r l i n e s = T a b l e ( #( 2 )
”order lines” ,
metadata ,
Column ( ” i d ” , I n t e g e r , p r i m a r y k e y=True , a u t o i n c r e m e n t=True ) ,
Column ( ” s k u ” , S t r i n g ( 2 5 5 ) ) ,
Column ( ” q t y ” , I n t e g e r , n u l l a b l e=F a l s e ) ,
Column ( ” o r d e r i d ” , S t r i n g ( 2 5 5 ) ) ,
)
...
def s t a r t m a p p e r s ( ) :
l i n e s m a p p e r = mapper ( model . O r d e r L i n e , o r d e r l i n e s ) #( 3 )
def t e s t o r d e r l i n e m a p p e r c a n s a v e l i n e s ( s e s s i o n ) :
n e w l i n e = model . O r d e r L i n e ( ” o r d e r 1 ” , ”DECORATIVE=WIDGET” , 1 2 )
s e s s i o n . add ( n e w l i n e )
s e s s i o n . commit ( )
repo = r e p o s i t o r y . SqlAlchemyRepository ( s e s s i o n )
r e p o . add ( b a t c h ) #( 1 )
s e s s i o n . commit ( ) #( 2 )
def t e s t r e p o s i t o r y c a n r e t r i e v e a b a t c h w i t h a l l o c a t i o n s ( s e s s i o n ) :
orderline id = insert order line ( session )
b a t c h 1 i d = i n s e r t b a t c h ( s e s s i o n , ” batch1 ” )
i n s e r t b a t c h ( s e s s i o n , ” batch2 ” )
i n s e r t a l l o c a t i o n ( s e s s i o n , o r d e r l i n e i d , b a t c h 1 i d ) #( 2 )
repo = r e p o s i t o r y . SqlAlchemyRepository ( s e s s i o n )
r e t r i e v e d = repo . get ( ” batch1 ” )
SqlAlchemyRepository:
class SqlAlchemyRepository ( AbstractRepository ) :
def i n i t ( self , session ):
self . session = session
def add ( s e l f , b a t c h ) :
s e l f . s e s s i o n . add ( b a t c h )
def g e t ( s e l f , r e f e r e n c e ) :
r e t u r n s e l f . s e s s i o n . q u e r y ( model . Batch ) . f i l t e r b y ( r e f e r e n c e=r e f
def l i s t ( s e l f ) :
# e x t r a c t o r d e r l i n e from r e q u e s t
l i n e = OrderLine (
request . json [ ’ orderid ’ ] ,
r e q u e s t . j s o n [ ’ sku ’ ] ,
request . json [ ’ qty ’ ] ,
)
# l o a d a l l b a t c h e s from t h e DB
# c a l l our domain s e r v i c e
a l l o c a t e ( line , batches )
r e t u r n 201
• Domain model.
• Repsitory interface.
@ p y t e s t . mark . u s e f i x t u r e s ( ” r e s t a r t a p i ” )
def t e s t a p i r e t u r n s a l l o c a t i o n ( a d d s t o c k ) :
sku , o t h e r s k u = r a n d o m s k u ( ) , r a n d o m s k u ( ” o t h e r ” ) #( 1 )
earlybatch = random batchref (1)
r = r e q u e s t s . p o s t ( f ” { u r l } / a l l o c a t e ” , j s o n=d a t a )
a s s e r t r . s t a t u s c o d e == 201
a s s e r t r . j s o n ( ) [ ” b a t c h r e f ” ] == e a r l y b a t c h
# f i r s t o r d e r u s e s up a l l s t o c k i n b a t c h 1
r = r e q u e s t s . p o s t ( f ” { u r l } / a l l o c a t e ” , j s o n=l i n e 1 )
a s s e r t r . s t a t u s c o d e == 201
a s s e r t r . j s o n ( ) [ ” b a t c h r e f ” ] == b a t c h 1
@ p y t e s t . mark . u s e f i x t u r e s ( ” r e s t a r t a p i ” )
def t e s t 4 0 0 m e s s a g e f o r o u t o f s t o c k ( a d d s t o c k ) : #( 1 )
sku , s m a l L b a t c h , l a r g e o r d e r = r a n d o m s k u ( ) , r a n d o m b a t c h r e f ( ) , r
add stock (
[ ( s m a l L b a t c h , sku , 1 0 , ”2011 = 01 = 01” ) , ]
)
d a t a = { ” o r d e r i d ” : l a r g e o r d e r , ” s k u ” : sku , ” q t y ” : 20 }
url = config . g e t a p i u r l ()
r = r e q u e s t s . p o s t ( f ” { u r l } / a l l o c a t e ” , j s o n=d a t a )
a s s e r t r . s t a t u s c o d e == 400
@ p y t e s t . mark . u s e f i x t u r e s ( ” r e s t a r t a p i ” )
def t e s t 4 0 0 m e s s a g e f o r i n v a l i d s k u ( ) : #( 2 )
unknown sku , o r d e r i d = r a n d o m s k u ( ) , r a n d o m o r d e r i d ( )
d a t a = { ” o r d e r i d ” : o r d e r i d , ” s k u ” : unknown sku , ” q t y ” : 20 }
url = config . g e t a p i u r l ()
r = r e q u e s t s . p o s t ( f ” { u r l } / a l l o c a t e ” , j s o n=d a t a )
a s s e r t r . s t a t u s c o d e == 400
a s s e r t r . j s o n ( ) [ ” message ” ] == f ” I n v a l i d s k u { unknown sku } ”
import config
import model
import orm
import repository
orm . s t a r t m a p p e r s ( )
g e t s e s s i o n = s e s s i o n m a k e r ( b i n d=c r e a t e e n g i n e ( c o n f i g . g e t p o s t g r e s u r i (
app = F l a s k ( n a m e )
def i s v a l i d s k u ( sku , b a t c h e s ) :
r e t u r n s k u i n {b . s k u f o r b i n b a t c h e s }
i f not i s v a l i d s k u ( l i n e . sku , b a t c h e s ) :
r e t u r n { ” message ” : f ” I n v a l i d s k u { l i n e . s k u } ” } , 400
try :
b a t c h r e f = model . a l l o c a t e ( l i n e , b a t c h e s )
except model . OutOfStock a s e :
r e t u r n { ” message ” : s t r ( e ) } , 400
s e s s i o n . commit ( )
r e t u r n { ” b a t c h r e f ” : b a t c h r e f } , 201
Steps:
def i s v a l i d s k u ( sku , b a t c h e s ) :
r e t u r n s k u i n {b . s k u f o r b i n b a t c h e s }
def a l l o c a t e ( l i n e : O r d e r L i n e , r e p o : A b s t r a c t R e p o s i t o r y , s e s s i o n ) => st
b a t c h e s = r e p o . l i s t ( ) #( 1 )
i f not i s v a l i d s k u ( l i n e . sku , b a t c h e s ) : #( 2 )
r a i s e I n v a l i d S k u ( f ” I n v a l i d s k u { l i n e . s k u }” )
b a t c h r e f = model . a l l o c a t e ( l i n e , b a t c h e s ) #( 3 )
s e s s i o n . commit ( ) #( 4 )
return b a t c h r e f
try :
b a t c h r e f = s e r v i c e s . a l l o c a t e ( l i n e , re po , s e s s i o n ) #( 2 )
r e t u r n { ” b a t c h r e f ” : b a t c h r e f } , 201 (3)
r = r e q u e s t s . p o s t ( f ” { u r l } / a l l o c a t e ” , j s o n=d a t a )
a s s e r t r . s t a t u s c o d e == 201
a s s e r t r . j s o n ( ) [ ” b a t c h r e f ” ] == e a r l y b a t c h
@ p y t e s t . mark . u s e f i x t u r e s ( ” r e s t a r t a p i ” )
def t e s t u n h a p p y p a t h r e t u r n s 4 0 0 a n d e r r o r m e s s a g e ( ) :
unknown sku , o r d e r i d = r a n d o m s k u ( ) , r a n d o m o r d e r i d ( )
d a t a = { ” o r d e r i d ” : o r d e r i d , ” s k u ” : unknown sku , ” q t y ” : 20 }
url = config . g e t a p i u r l ()
r = r e q u e s t s . p o s t ( f ” { u r l } / a l l o c a t e ” , j s o n=d a t a )
a s s e r t r . s t a t u s c o d e == 400
With UoW:
Check Figure 2. With UoW: UoW now manages database
state
Benefit: Decouple service layer from the data layer.
service layer/services.py:
def a l l o c a t e (
o r d e r i d : str , sku : str , qty : int ,
uow : u n i t o f w o r k . A b s t r a c t U n i t O f W o r k ,
) => s t r :
l i n e = O r d e r L i n e ( o r d e r i d , sku , q t y )
w i t h uow : #( 1 )
b a t c h e s = uow . b a t c h e s . l i s t ( ) #( 2 )
...
b a t c h r e f = model . a l l o c a t e ( l i n e , b a t c h e s )
uow . commit ( ) #( 3 )
integration/test uow.py
def t e s t u o w c a n r e t r i e v e a b a t c h a n d a l l o c a t e t o i t ( s e s s i o n f a c t o r y ) :
session = session factory ()
i n s e r t b a t c h ( s e s s i o n , ” b a t c h 1 ” , ”HIPSTER=WORKBENCH” , 1 0 0 , None )
s e s s i o n . commit ( )
uow = u n i t o f w o r k . SqlAlchemyUnitOfWork ( s e s s i o n f a c t o r y ) #( 1 )
w i t h uow :
b a t c h = uow . b a t c h e s . g e t ( r e f e r e n c e=” b a t c h 1 ” ) #( 2 )
l i n e = model . O r d e r L i n e ( ” o1 ” , ”HIPSTER=WORKBENCH” , 1 0 )
batch . a l l o c a t e ( l i n e )
uow . commit ( ) #( 3 )
b a t c h r e f = g e t a l l o c a t e d b a t c h r e f ( s e s s i o n , ” o1 ” , ”HIPSTER=WORKBEN
a s s e r t b a t c h r e f == ” b a t c h 1 ”
@abc . a b s t r a c t m e t h o d
def commit ( s e l f ) : #( 3 )
ra is e NotImplementedError
@abc . a b s t r a c t m e t h o d
def r o l l b a c k ( s e l f ) : #( 4 )
ra is e NotImplementedError
c l a s s SqlAlchemyUnitOfWork ( A b s t r a c t U n i t O f W o r k ) :
def i n i t ( s e l f , s e s s i o n f a c t o r y=DEFAULT SESSION FACTORY ) :
s e l f . s e s s i o n f a c t o r y = s e s s i o n f a c t o r y #( 1 )
def commit ( s e l f ) : #( 4 )
s e l f . s e s s i o n . commit ( )
def r o l l b a c k ( s e l f ) : #( 4 )
s e l f . session . rollback ()
def a l l o c a t e (
o r d e r i d : str , sku : str , qty : int ,
uow : u n i t o f w o r k . A b s t r a c t U n i t O f W o r k , #( 1 )
) => s t r :
l i n e = O r d e r L i n e ( o r d e r i d , sku , q t y )
w i t h uow :
b a t c h e s = uow . b a t c h e s . l i s t ( )
i f not i s v a l i d s k u ( l i n e . sku , b a t c h e s ) :
r a i s e I n v a l i d S k u ( f ” I n v a l i d s k u { l i n e . s k u }” )
b a t c h r e f = model . a l l o c a t e ( l i n e , b a t c h e s )
uow . commit ( )
return b a t c h r e f
Not OK: allocate multiple order lines for the same product
Copyright © 2021, 2022 Lan Hui 107
DEADLY-SPOON at the same time.
Bounded Context:
def a l l o c a t e ( s e l f , l i n e : O r d e r L i n e ) => s t r : #( 3 )
try :
b a t c h = next ( b f o r b i n s o r t e d ( s e l f . b a t c h e s ) i f b . c a n a l l o
batch . a l l o c a t e ( l i n e )
return batch . r e f e r e n c e
except S t o p I t e r a t i o n :
r a i s e OutOfStock ( f ”Out o f s t o c k f o r s k u { l i n e . s k u } ” )
def g e t ( s e l f , s k u ) :
r e t u r n s e l f . s e s s i o n . q u e r y ( model . P r o d u c t ) . f i l t e r b y ( s k u=s k u ) . f i
def commit ( s e l f ) :
s e l f . s e s s i o n . commit ( )
def r o l l b a c k ( s e l f ) :
s e l f . session . rollback ()
def a l l o c a t e (
o r d e r i d : str , sku : str , qty : int ,
uow : u n i t o f w o r k . A b s t r a c t U n i t O f W o r k ,
) => s t r :
l i n e = O r d e r L i n e ( o r d e r i d , sku , q t y )
w i t h uow :
p r o d u c t = uow . p r o d u c t s . g e t ( s k u= l i n e . s k u )
i f p r o d u c t i s None :
r a i s e I n v a l i d S k u ( f ” I n v a l i d s k u { l i n e . s k u }” )
batchref = product . a l l o c a t e ( l i n e )
uow . commit ( )
r e t u r n { ” b a t c h r e f ” : b a t c h r e f } , 201
c l a s s Event : #( 1 )
pass
@dataclass
c l a s s OutOfStock ( E v e n t ) : #( 2 )
Note: product.events[-1].
Now let’s update model.py.
from . import e v e n t s
c l a s s Product :
def i n i t ( s e l f , s k u : s t r , b a t c h e s : L i s t [ Batch ] , v e r s i o n n u m b e r :
s e l f . sku = sku
s e l f . batches = batches
s e l f . version number = version number
s e l f . e v e n t s = [ ] # t y p e : L i s t [ e v e n t s . Event ]
def h a n d l e ( e v e n t : e v e n t s . E v e n t ) :
f o r h a n d l e r i n HANDLERS [ type ( e v e n t ) ] :
handler ( event )
def s e n d o u t o f s t o c k n o t i f i c a t i o n ( e v e n t : e v e n t s . OutOfStock ) :
email . send mail (
” stock@made . com” ,
f ”Out o f s t o c k f o r { e v e n t . s k u } ” ,
)
services.py:
from . import m e s s a g e b u s
...
def a l l o c a t e (
o r d e r i d : str , sku : str , qty : int ,
uow : u n i t o f w o r k . A b s t r a c t U n i t O f W o r k ,
) => s t r :
l i n e = O r d e r L i n e ( o r d e r i d , sku , q t y )
w i t h uow :
p r o d u c t = uow . p r o d u c t s . g e t ( s k u= l i n e . s k u )
i f p r o d u c t i s None :
services.py:
def a l l o c a t e (
o r d e r i d : str , sku : str , qty : int ,
uow : u n i t o f w o r k . A b s t r a c t U n i t O f W o r k ,
) => s t r :
l i n e = O r d e r L i n e ( o r d e r i d , sku , q t y )
w i t h uow :
p r o d u c t = uow . p r o d u c t s . g e t ( s k u= l i n e . s k u )
i f p r o d u c t i s None :
r a i s e I n v a l i d S k u ( f ” I n v a l i d s k u { l i n e . s k u }” )
batchref = product . a l l o c a t e ( l i n e )
uow . commit ( ) #( 1 )
c l a s s A b s t r a c t U n i t O f W o r k ( abc . ABC ) :
products : repository . AbstractRepository
def p u b l i s h e v e n t s ( s e l f ) :
for product in s e l f . products . seen :
while product . events :
e v e n t = p r o d u c t . e v e n t s . pop ( 0 )
messagebus . handle ( event )
@abc . a b s t r a c t m e t h o d
def commit ( s e l f ) :
ra is e NotImplementedError
@abc . a b s t r a c t m e t h o d
def r o l l b a c k ( s e l f ) :
ra is e NotImplementedError
• BatchCreated
• BatchQuantityChanged
• AllocationRequired
• OutOfStock
Copyright © 2021, 2022 Lan Hui 132
Each of the above events can be denoted as a dataclass.
# add batch
event = events.BatchCreated(
request.json["ref"],
request.json["sku"],
request.json["qty"], eta
)
messagebus.handle(event, unit_of_work.SqlAlchemyUnitOfWork())
messagebus.handle(event, unit_of_work.SqlAlchemyUnitOfWork())
HANDLERS = {
events . BatchCreated : [ h a n d l e r s . add batch ] ,
e v e n t s . BatchQuantityChanged : [ h a n d l e r s . c h a n g e b a t c h q u a n t i t y ] ,
events . AllocationRequired : [ handlers . allocate ] ,
e v e n t s . OutOfStock : [ h a n d l e r s . s e n d o u t o f s t o c k n o t i f i c a t i o n ] ,
} # t y p e : D i c t [ Type [ e v e n t s . Event ] , L i s t [ C a l l a b l e ] ]
Examples:
class Command:
pass
@dataclass
class Allocate(Command): #(1)
orderid: str
sku: str
qty: int
@dataclass
class ChangeBatchQuantity(Command): #(3)
COMMAND HANDLERS = {
commands . A l l o c a t e : h a n d l e r s . a l l o c a t e ,
commands . C r e a t e B a t c h : h a n d l e r s . a d d b a t c h ,
commands . C h a n g e B a t c h Q u a n t i t y : h a n d l e r s . c h a n g e b a t c h q u a n t i t y ,
} # t y p e : D i c t [ Type [ commands . Command ] , C a l l a b l e ]
def h a n d l e ( #( 1 )
message : Message ,
uow : u n i t o f w o r k . A b s t r a c t U n i t O f W o r k ,
):
results = []
queue = [ message ]
w h i l e queue :
message = queue . pop ( 0 )
i f i s i n s t a n c e ( message , e v e n t s . E v e n t ) :
h a n d l e e v e n t ( message , queue , uow ) #( 2 )
e l i f i s i n s t a n c e ( message , commands . Command ) :
c m d r e s u l t = handle command ( message , queue , uow ) #( 2 )