I've been recently trying to go slightly further in arch. patterns (specifically layered arch.). I decided to go though an old project of mine (a CLI-based cinema reservation system) and evaluate my design from back then.
This an ambitious attempt to implement the ECB pattern (Entity-Control-Boundary Pattern), i didn't go all the way with it though (as you can see in the Movie and Reservation classes). I understand the differences between ECB and MVC, generally revolving around what to be encapsulated in the model/entity, plus the rules of directionality between the layers.
This is essentially the class diagram I started out with:
I refactored this to ECB (from my understanding) as follows:
Note: It should have been
My final DCD is as follows (note: I didn't apply ECB on the Movies and Reservation classes):
I have 4 main questions regarding this design: (would appreciate to hear the best practices from you)
1. Is this an acceptable design of the ECB pattern?
- A new user can either create account or login
- A logged in user can: logout, make reservation, view reservations, view movies
- I decided to separate the user interface logic for when: 1.a user is new, 2. a user is logged in, so I created the two boundary classes.
- I made the entity classes (talking about Account only here; I didn't refactor Movie and Reservation into ECB) to contain only attributes and getters/setters.
- I placed the business logic (again referring to the Account entity) into a separate
Controlclass based on each use case (login, registration).
2. Are these boundaries reusable for a future integration?
create_account_inputsare all CLI methods that take inputs from end-user and pass it (based on the chosen command/service from the end-user as well) to the relevant method of the relevant controller.
Say that I would like to add a web framework like flask to this system, I believe that the boundary classes, with a better design (e.g.: having an interface or a base class) can be reused, and the input methods would be implemented differently to instead, fetch input field values from forms via POST requests. Please correct me if I'm wrong.
3. Is it right for my driver code to instantiate all the needed objects, especially Entity objects, and perform the associations accordingly
Here's a snippet of my
bootstrap.py, which wires everything together:
# ===================== SYSTEM IMPORTS ======================== from Account import Account from CreateAccountController import CreateAccountController from LoggedInAccountController import LoggedInAccountController from NewAccountUI import NewAccountUI from LoggedinAccountUI import LoggedinAccountUI from Movie import Movie import time # =================== Object Initialization =================== acc = Account() # entity ca_cont = CreateAccountController(acc) na_ui = NewAccountUI(ca_cont) mov = Movie() lg_cont = LoggedInAccountController(acc, mov) lg_ui = LoggedinAccountUI(lg_cont) ...
- I chose to implement the relationships between my classes as associations, to lower the coupling (+ imports) in the system internal classes, but the trade-off was that the coupling in the driver code became really high hence the imports. But what would be the alternative?
4. Would this be considered an acceptable DAO design and implementation ?
- I included the same ECB-based system diagram from Figure 3, but with the focus only on the Entities in the system, as shown in Figure 4 (see attributes highlighted in red)
- Since I was working with JSON files at the time, I resorted to a single concrete DAO, I named it
StoredFiles, the constructor takes a JSON filepath, reads its content and store it in an instance variable called
# StoredFiles import json import os cwd = os.path.dirname(os.path.realpath(__file__)) class StoredFiles: def __init__(self, filename): self.stored_file = cwd+"\\"+filename self.memory = self.read() def read(self): afile = open(self.stored_file, 'r') return json.load(afile) def get_all(self): return self.memory def update(self, **kwargs): .... def write(self): .... def search(self, key, val): ...
- The idea was to create a DAO or a
StoredFilesobject for every entity.
- In each entity, a
StoredFilesobject is instantiated by passing the relevant JSON file as an argument to the constructor.
- I chose to link Entities to their DAO objects via the
<<create>>usage dependency relationship, and to keep them as class (or shared) attributes.
from StoredFiles import StoredFiles class Account: accounts = StoredFiles("data.json") def __init__(self): ...
from StoredFiles import StoredFiles class Movie: movies = StoredFiles("movies.json") def __init__(self): ...
And so on..
- I could have them linked via Composition (as instance variables) but from a conceptual/logical perspective, composition didn't feel right to me.
So essentially the control flow is as follows:
Boundary -> Control -> Entity -> StoredFiles/DAO
IMPORTANT NOTE I've looked up ALOT of resources online, what I came to realize is (please correct me if im in the wrong or I have missing information) that DAOs essentially need to have an interface (or an abstract class) and should be implemented (or sub-classed) for every entity in the system. And now by looking at current flow described in this system I'm presenting, it seems slightly incorrect towards the end, and the correct one should be:
Boundary -> Control -> StoredFiles/DAO -> Entity
But from an analytical POV, taking this design/flow as it is, what is the good and the bad about it? If I stayed with the same design how hard would it be to workaround a new persistence, e.g.: an SQL engine for example? What is a best practice that I can follow to recreate this DAO.