Software Architecture - herougo/SoftwareEngineerKnowledgeRepository GitHub Wiki
- (optional) https://www.youtube.com/watch?v=o_TH-Y78tt4
An architecture for software where you have the following layers.
- Domain (enterprise-level business rules): includes entities, enterprise-level exceptions, enums, value objects, events, etc
- Application (application-specific business rules): use cases (commands, queries, event handlers)
- Interface Adapters: includes controllers, presenters, gateways
- Frameworks and Drivers: includes external interfaces, UI, DB, Web, devices
The point is that we have a single direction of dependency for the layers (i.e. 4 depends on 3, which depends on 2, which depends on 1). 2 never depends on something from 3 for example.
To make use of a database in the application layer for example, you have interfaces which are implemented in layer #4. You pass those objects as arguments to functions, constructors, etc. This way, the DB is mockable.
Advantages of clean architecture:
- highly testable
- framework independent
- DB independent (database should be a detail/plugin)
- UI independent
Disadvantages ( https://www.geeksforgeeks.org/what-is-clean-architecture-in-android/ )
- steep learning curve (i.e. learning how all levels interact)
- many extra classes
The video mentioned above refers to an "interactor" object. What is an interactor exactly? I'm not sure, but it seems like a black box which receives input, processes entities using business rules, then returns an output.
Model-View-Presenter Pattern: have a presenter module between the model and view, which transforms the data into something usable by the view (e.g. converting objects to strings).
Command - a directive to a computer program to perform a specific task
Query - request for information
Instead of having 4 layers, we have 3 layers, with the outermost layer being split into 2 parts (infrastructure and UI).
- Domain
- Application
- Infrastructure / UI
Project structure (for to do items as an example)
- Domain: folders for entities, enums, events, exceptions, and value objects
- Application:
- folder structure: /<feature_type>/...
- entity can be TodoItems or TodoLists
- feature_type can be query, command, or event handler
- folders in common folder: behaviours, exceptions, interfaces, mappings, models, security
- folder structure: /<feature_type>/...
- infrastructure
- contains persistence, date service, etc
- WebUI
- contains angular code, controllers, etc
Advantages
- (advantages of the clean architecture)
Disadvantages
- Many files: one file for the validator, one file for the command itself, etc
Questions:
- What are the behaviours, interfaces, mappings, and models folders about?
- How would I build this from scratch (how is the code set up from a high level)?
Cons
- Can make coding more difficult (especially in python). Let's say you want to use Datadog to do some logging. Traditionally, you would create a datadog_logging.py file and write your logger wrapper, create a global variable as an instance of that wrapper class, then import that global variable whenever you need to log something. However, this goes against the clean architecture paradigm (I think) because you're depending on something from the outermost layer. Furthermore, if you try dependency injection, well now you need to pass around this logger everywhere in the arguments.
- I think this is mainly done for Java, and in python, which has much more flexibility, this makes coding more difficult.
Basic idea:
- suppose we have the layers as horizontal
- consider organizing the code by features and taking vertical slices
His project structure
- Application
- Common
- Entities
- Features
- TodoItems
- CreateTodoItem.cs (contains controller, command, validator, event, etc in one file)
- ...
- TodoLists
- TodoItems
- Infrastructure
- ValueObjects
- Web UI
Related Article for making this work in C#: https://scottsauber.com/2016/04/25/feature-folder-structure-in-asp-net-core/
This implementation of a python game using MVC uses an EventManager to manage the events.
https://github.com/wesleywerner/mvc-game-design
ev = EventManager()
model = GameModel(ev) # ev register listener
controller = GameController(ev, model) # ev register listener
view = GameView(ev, model) # ev register listener
model.run()Thoughts
- Con: ev registers all as listeners
- I think the config setup of event management should live inside the controller
Status: Work In Progress
- problem: How does MVCEventManager work?
controller = GameController()
controller.run()
class EventReceiver:
def notify(self, event):
pass
class GameController(EventReceiver):
def __init__(self):
self._event_manager = MVCEventManager()
self._model = GameModel(self._event_manager)
self._view = GameView(self._event_manager)
self._event_manager.register_mvc(model, view, self)
# controller -> model -> view
def register_click(self):
passsource: https://www.milanjovanovic.tech/blog/what-is-a-modular-monolith
A modular monolith is an architectural pattern that structures the application into independent modules or components with well-defined boundaries. The modules are split based on logical boundaries, grouping together related functionalities. This approach significantly improves the cohesion of the system.
The modules are loosely coupled, which further promotes modularity and separation of concerns. Modules communicate through a public API.
Example folder structure
- components
- payments
- public_api.py?, controllers, validators, etc
- reviews
- users
- bookings
- notifications
- payments
Modular monoliths have many benefits. So, I want to highlight a few that I consider important:
- Simplified deployment - Unlike microservices, which require complex deployment strategies, a modular monolith can be deployed as a single unit.
- Improved performance - Communication between modules occurs in-process. This means that there's no network latency or data serialization/deserialization overhead.
- Enhanced development velocity - There's a single codebase to manage, simplifying debugging and the overall development experience.
- Easier transaction management - Managing transactions in a distributed system is very challenging. Modular monoliths simplify this since modules can share the same database.
- Lower operational complexity - Modular monoliths reduce the operational overhead that comes with managing and deploying a distributed microservices system.
- Easier transition to Microservices - A well-structured modular monolith offers a clear path to a microservices architecture. You can gradually extract modules into separate services when the need arises.
Folder Structure Before
- app
- assets
- channels
- controllers
- helpers
- jobs
- mailers
- models
- views
- bin
- config
- db
- lib
- test
Folder Structure After
- components
- apps
- billing
- checkouts
- taxes
- app
- assets
- channels
- controllers
- helpers
- jobs
- mailers
- models
- views
- config
- lib
- test
- app
- bin
- config
- db
- lib
- test
Monolith disadvantages
- Simple changes can result to a cascade of test failures.
- Steep learning curves
- Scalability (can't scale individual components)
- Deployment (can't deploy frequently)
