Python App Architecture Framework
45sIntroduces a reusable architecture pattern that developers can apply to their own projects.
▶ Play ClipThis video concludes a series on Python project structure by introducing a general architecture framework inspired by the book 'Architecture Patterns with Python'. The framework organizes code into three layers: domain (business logic), services (orchestration), and adapters (external integrations). The presenter demonstrates refactoring a shopping cart app to use this structure and adds a scoring adapter as an example.
The framework includes a domain folder for business logic, a services folder for application logic, and an adapters folder for external integrations.
The idea comes from the book 'Architecture Patterns with Python', which covers advanced topics like unit of work but also provides basic structuring ideas.
The domain layer defines business logic. In the shopping cart example, fruits and shopping carts are domain entities.
The service layer defines jobs the system performs and orchestrates components. For the shopping cart, adding and removing items are services.
Adapters provide concrete implementations of interfaces that connect the system to the outside world, such as databases or external APIs.
The presenter creates folders for domain, services, and adapters, moves files accordingly, and updates imports to make the app work.
An adapter is created to score a shopping cart based on item prices, demonstrating how to integrate external logic.
The presenter shows how to support multiple scoring strategies (e.g., base and Apex) using a factory function in the adapters layer.
The framework helps structure Python apps into domain, services, and adapters, making code more maintainable and scalable. The presenter encourages viewers to apply these ideas to their own projects.
"Title accurately describes the content: a general architecture framework for Python apps."
What are the three layers in the architecture framework described?
Domain, services, and adapters.
00:17
What does the domain layer contain?
Business logic and domain entities (e.g., fruit, shopping cart).
01:10
What is the purpose of the services layer?
It defines jobs the system should perform and orchestrates different components.
01:22
What do adapters do?
They provide concrete implementations of interfaces that connect the system to the outside world (e.g., databases, external APIs).
01:39
Which book inspired this framework?
Architecture Patterns with Python.
00:37
How does the presenter demonstrate using multiple adapters?
By creating a factory function (get_scorer) that returns different scorer implementations based on a score type string.
10:25
Book Inspiration
The framework is derived from a well-known Python architecture book, lending credibility.
00:37Domain Layer Definition
Clear explanation of what constitutes business logic in a simple shopping cart example.
01:10Adapter Example
Practical demonstration of building an adapter for scoring, showing real-world application.
04:10Multiple Adapters Pattern
Shows how to support multiple implementations using a factory, a key design pattern.
08:47[00:00] hey everybody welcome back in this final
[00:02] part of my python project structure
[00:04] video series I'm going to look at
[00:06] extending the app we build just a little
[00:08] bit but using a general framework that
[00:11] you can use for all of your applications
[00:13] or scripts that you write in Python what
[00:15] we're going to be doing is adding a
[00:17] domain folder to group all of the domain
[00:20] Logic for our app uh Services folder
[00:23] which is going to have all of the sort
[00:25] of logic of our app and then an adapters
[00:27] folder which is going to include things
[00:29] that external to our app that we want to
[00:31] bring in and use in here this is a
[00:33] general purpose framework that you can
[00:35] use to structure your projects I got
[00:37] this idea from this architectures book
[00:39] for python the ideas discussed in this
[00:41] book apply to Big monolithic systems
[00:44] that are really complicated there's
[00:46] Advanced topics like a unit of work they
[00:49] have a lot of abstractions that make
[00:51] these really big systems work nicely so
[00:54] if this is so complicated and advanced
[00:55] why am I talking about it well there's a
[00:57] few basic ideas that I gleamed from here
[01:00] which I apply to my projects and
[01:02] basically just help me structure things
[01:05] so I've already talked about them in my
[01:06] own words but I'm going to use the
[01:08] author's words there's a domain layer
[01:10] this defines the business logic in our
[01:12] case with our example of a shopping cart
[01:15] with fruits in it the business logic is
[01:17] this concept of a fruit and a shopping
[01:20] cart these are our domain entities next
[01:22] we'll have a service later this defines
[01:24] the jobs the system should perform and
[01:26] orchestrates different components in our
[01:29] case we want want to add items to the
[01:31] shopping cart and remove them it's
[01:32] pretty much it so that's our service
[01:34] layer although the authors have more in
[01:36] here the last one that I use regularly
[01:39] is an adapter's layer concrete
[01:42] implementations of an interface that
[01:44] goes from our system to the outside
[01:46] world all right so the authors are
[01:49] describing this as a way of of of their
[01:52] system talking to the outside world this
[01:54] could be like a database integration
[01:56] where you want your system to write data
[01:58] to a database
[02:00] but for me I also use adapters to pull
[02:02] data in from the outside world so let's
[02:04] go ahead and restructure our project to
[02:07] work like this it's really easy first
[02:09] I'm going to hop into our shopping cart
[02:11] and make these folders that I've been
[02:12] describing I'll have one for domain
[02:15] Services adapters typing inside of each
[02:18] of these I'm going to put an init
[02:24] file and now the fruit file we just want
[02:27] to put this directly into adapters into
[02:29] here okay let's move my shopping cart
[02:31] now into Services let's check out fruit
[02:33] oops I accidentally put that in adapters
[02:36] okay that's in domain this is clearly
[02:38] like a domain entity and shopping cart
[02:41] is like a service right so I put that in
[02:42] here but I've got this domain object
[02:45] inside of here let's move that over
[02:48] shoppingcart
[02:50] py let's open that and paste and this is
[02:53] good because fruit is local now right to
[02:55] this domain so we we've got all our
[02:57] domain stuff together in one place and
[02:59] and now I I just want to make sure we
[03:01] get rid of it here get rid of that
[03:03] probably don't need data classes now I
[03:05] don't have my shopping cart I'm going to
[03:07] go to my domain shopping import shopping
[03:11] cart so here's my project so if I run my
[03:14] app so it's not finding a module named
[03:17] shopping cart. shopping cart anymore and
[03:20] maybe this is my init files I never
[03:22] updated that so let's go back in this
[03:25] init file I'm grabbing some stuff from
[03:27] shopping cart now in my services like so
[03:30] I have to update that and for fruit
[03:33] that's in my domain now see let's try it
[03:34] again fruit is not defined I suppose
[03:37] what I did do inside of my shopping cart
[03:40] service I probably have a fruit object
[03:44] here just instead of shopping cart I
[03:49] want fruit right and uh I'll do it this
[03:54] way just grab it like that still feeling
[03:56] pretty good U I'm not really going to
[03:57] think too much about it let's try and
[03:59] run it again great it worked so my app
[04:01] is still working after refactoring it
[04:04] well let's throw together a quick
[04:05] example on the Fly of how we might want
[04:07] to have an adapter for this application
[04:10] let's imagine we're like an e-commerce
[04:11] website where users will have a shopping
[04:13] cart what you might want to do is
[04:15] classify those users for like a
[04:17] propensity of how valuable they are
[04:19] maybe all the items in the shopping cart
[04:21] are worth like a lot of money so we
[04:22] should kind of identify that let me go
[04:24] ahead and build this starting in the
[04:26] services layer score shopping cart and
[04:30] let's add some documentation assign a
[04:33] score to the shopping cart based on the
[04:39] contents like the other functions here
[04:42] we want to add a shopping cart we're
[04:45] passing in like what our current cart is
[04:47] now what I want to do is score it here's
[04:49] where our adapters layer is going to
[04:51] come in so I'll say from adapters score
[04:55] and I'm going to say import shopping
[04:57] card scorer so no I'm using camel case
[05:02] or rather like python convention for an
[05:04] object goe and initialize it in here
[05:06] score do score and I'm going to pass in
[05:10] my cart this will provide me score I'll
[05:12] call it cart
[05:14] score I'm going to assign this to the
[05:16] shoing cart object let's remind oursel
[05:19] of what that looks like so now I could
[05:20] have a score down here and I'm going to
[05:22] call that int it's going to be an
[05:23] integer score and it's going to default
[05:25] To None because we don't need to have a
[05:28] score when we're initializing shopping
[05:30] cart let's go back to our services cart
[05:33] doore equals
[05:35] this assigning score cart
[05:40] score okay so what do we have to do now
[05:42] well this shopping cart score doesn't
[05:44] exist we need to create it and we need
[05:46] to make sure that it has this right
[05:47] method I'm going to create a score file
[05:51] and we know we need these things so
[05:52] class shoing card scorer is going to
[05:55] come in here and this is going to have
[05:57] to do some stuff let is going to have to
[05:59] to have this
[06:03] method okay it's going to pick itself
[06:06] and a cart when we initialize
[06:08] this I'm not going to actually do
[06:10] anything when we initialize this and
[06:12] then for this guy here um we need to
[06:15] make sure he's returning an integer I
[06:17] could just say that score is going to
[06:19] equal and I could say item. price for
[06:24] item in cart.
[06:27] items and I could say or zero because
[06:32] the price might be none and then I can
[06:34] just sum those up and I could return
[06:37] that as a score one last thing I want to
[06:40] do is pass that as an integer or I guess
[06:43] to this would be more in line with the
[06:46] Zen of python to do things this way
[06:48] pretty happy with with everything here
[06:50] even though I'm not really using my
[06:51] initialization method in general I might
[06:53] want to do that now one other thing I
[06:55] might want to do here is say from domain
[06:59] do shopping cart import go here and
[07:04] explicitly say what we're passing in
[07:07] here um let's see if everything uh works
[07:09] if I run my app that seems to be working
[07:11] the thing is my app's not actually doing
[07:14] anything we're going to need to import
[07:16] that so if I go back to my services here
[07:19] it is score shopping cart is what I want
[07:22] put that here okay so I'm just going to
[07:26] import it along with everything else and
[07:28] then if I go to the again and grab the
[07:30] names now where am I doing okay so now I
[07:33] want to score this and I want to score
[07:35] my card and that was it I didn't have
[07:36] any other arguments so after I do that
[07:38] if I print my cart
[07:40] again and I'll just say
[07:43] print and we'll be able to see
[07:46] that see if it'll work it can't import
[07:49] that I believe it's because I didn't add
[07:51] it to my
[07:53] init okay so score shopping cart cool
[07:57] let's see so here it's assigning a score
[07:59] zero because um I have no prices when I
[08:01] add my fruits I'll just assign some
[08:04] arbitrary prices say $99 because these
[08:07] are the most fantastic fruits in the
[08:09] world and there's my score of 198 you
[08:11] could stop watching here and I wouldn't
[08:13] blame you and if you do I thank you for
[08:16] going this far with me I would really
[08:18] appreciate it if you consider giving
[08:20] these videos a like or maybe subscribing
[08:22] to my channel if you like this content
[08:24] as a new channel this really helps me
[08:27] get visibility if you liked my layout
[08:29] with the terminal I have another video
[08:32] on using vim and t-o and how I've
[08:35] configured these things it will send
[08:37] your productivity into an absolute
[08:39] spiraling death I mean um I mean uh you
[08:43] should check that out a little bonus the
[08:45] last thing I'm going to do is make this
[08:47] adapter better we might want to have
[08:49] different ways of scoring things what
[08:51] I've created here is more of like a base
[08:54] score it's almost more like a unit test
[08:56] really I make a new folder and I'm going
[08:58] to call it Gore
[09:00] plural and then inside of here I'm going
[09:02] to I'm going to copy this guy and I'm
[09:04] going to put it in here delete I want to
[09:06] call this a base score rename it and I'm
[09:08] going to rename it base I guess py
[09:11] called this base shopping cart score I'm
[09:14] going to have to go down an additional
[09:16] level so I'll add another dot there
[09:18] where am I going with this well I might
[09:20] want to have different scores I'm going
[09:21] to copy this guy and I'm going to paste
[09:22] it what do we want to call another score
[09:25] Apex I don't know what that means but
[09:27] let's say we have some model we've
[09:29] called it Apex okay so let's call that
[09:31] Apex shopping cart score so I could
[09:34] imagine one situation where I have a
[09:37] different projects set up for this Apex
[09:39] and I can say from Apex scorer import
[09:43] shopping cart
[09:46] score and then I could go down here and
[09:49] where I have score shopping cart I just
[09:52] want to return the Apex call to shopping
[09:57] cart score we're assuming that we have
[10:00] this somewhere else on our system and
[10:02] all we want to do is call that here and
[10:05] so this adapter lets us do that in a
[10:08] nice structured way so we have our Apex
[10:11] shopping card score object and we also
[10:14] have a base scoring object which does
[10:16] the same thing it has a scoring shopping
[10:18] cart function so these are implementing
[10:21] the same thing which is going to be
[10:23] important for what we do next so I'll
[10:25] have scorer. py so in here I'm going to
[10:27] create a new function score shop cing
[10:30] card I want a function that's going to
[10:31] bring in my objects from my subfolder in
[10:33] this case from do
[10:37] scores and then I'll do the same thing
[10:39] for
[10:40] base now in here I want a score type gu
[10:44] string I'm going to return a score a
[10:47] union of one of these so B basically it
[10:50] means it's one of these two things this
[10:52] is not really super clean I wouldn't
[10:54] actually want to do it this way but I'll
[10:56] say if the score type
[11:00] is equal to Apex bring that into our
[11:03] application and else if this is B like
[11:08] that okay good everybody's happy so I'm
[11:11] typing have Union if we haven't returned
[11:14] anything I'm going to raise a value
[11:16] error and a
[11:18] type and just a little exclamation mark
[11:21] and actually what I really want to do is
[11:23] I want to get the scar this is going to
[11:25] return an object and then I'm going to
[11:27] have to call that object still so by
[11:30] doing this the last thing I want to do
[11:33] is fetch that here and I can say in my
[11:35] init file for adapters I'll say from
[11:38] scorer I'll import um get scorer and uh
[11:43] let's go down and Implement that in our
[11:44] services now in my adapter's file I just
[11:48] put that in my init file is get score I
[11:52] can just fetch that directly out of my
[11:54] adapters now here is the shopping cart
[11:56] score no longer like that it's get score
[11:58] and I need a score type so score type
[12:01] and we're going to use um let's surface
[12:04] this type up to here and score type and
[12:09] it's going to be like a string we said
[12:11] so now that just kind of is going to get
[12:13] passed in from here
[12:18] oops okay happy with that and we're
[12:20] going to score our cart base on this
[12:23] score which is returned from here now we
[12:25] need to add the scor type to our app
[12:28] wherever I score my shopping cart here
[12:31] um I need my score type to be defined
[12:35] what I could do is make this a global
[12:36] variable in in my in my app here and I
[12:39] I'm going to say we're going to use the
[12:41] base one we know what to expect if I've
[12:43] done everything right this should just
[12:45] work okay it's saying no module named
[12:48] Apex scorer
[12:50] oh of course there's no module named
[12:52] Apex score because we just created that
[12:55] right it it it doesn't actually exist
[12:57] for the purposes of getting this working
[12:58] let's just like I don't know why I
[13:00] didn't see that coming leave that and
[13:03] say return zero and Implement score see
[13:07] if that does the trick uh score shopping
[13:10] cart is missing a cart argument okay
[13:14] yeah this is a weird one this is an
[13:16] object or this is a class you could call
[13:19] it score class just so you understand
[13:21] what's happening right and I need to
[13:22] initialize that I was not doing that
[13:25] that's what I want to do
[13:27] okay we're working here this is
[13:30] fantastic and one last thing I could do
[13:33] just for my own edification is in my app
[13:37] I want to try the other scor type our
[13:40] awesome Apex model which
[13:43] returns
[13:44] zero like we see here oh yeah okay this
[13:48] was a lot of fun as you can see it's got
[13:50] quite dark outside I have another video
[13:53] that talks about my t-u and my Vim
[13:55] config which has eaten up massive hours
[13:58] of my evenings this week and I think vim
[14:01] and t-o configs could eat up massive
[14:03] hours of your evening as well it has
[14:05] been fantastic to go through this demo
[14:08] and I hope you take these ideas and use
[14:10] them in your coding and build better
[14:12] python applications thanks so much and
[14:16] see you in the next one
⚡ Saved you time reading this? Transcribe any YouTube video for free — no signup needed.