3
\$\begingroup\$

I am working on a simple CRUD app as a personal project using Flask. I am currently working on the user blueprint and trying to use as less as libraries as I could and no ORM (for learning purposes). On my blueprints, I have multiple folders including a user folder. I wrote 2 classes in blueprints/user/models.py:

User: represent a User entity

UserService : register and login (login is not present yet)

  • Does it make sense to have2 separate classes? I am actually thinking to move all of my User methods to the UserService class and just have a UserService class with setters and getters.

  • I am confused about is where and when to interact with the database. Should I do it directly on my route.py vs create a new class?

For instance, to add a new User to the database, my SQL should look more or less like this:

sql = "INSERT INTO users (email, password) VALUES (%s, %s)"
cursor = conn.cursor()
cursor.execute(sql, (email, password))
cursor.fetchall()
conn.commit()

blueprints/user/models.py

class UserService():
    def register_user(self, 
                      email, 
                      password, 
                      registration_date, 
                      active, 
                      sign_in_count, 
                      current_sign_in_on,  
                      last_sign_in_on):
        new_user = User(email, password, registration_date, active, sign_in_count, current_sign_in_on, last_sign_in_on)
        return new_user.__str__()
    

    
class User():
    def __init__(self, email, password, registration_date, active, sign_in_count, current_sign_in_on, last_sign_in_on ):
        self.email = email
        self.password = password
        self.registration_date = registration_date
        self.active = active

        # Activity tracking
        self.sign_in_count = sign_in_count
        self.current_sign_in_on = current_sign_in_on
        self.last_sign_in_on = last_sign_in_on

    def desactivate_user(self):
        if self.active == False:
            print(f"User {self.email} is already inactive")
        self.active = False

    def reactive_user(self):
        if self.active == True:
            print(f"User {self.email} is already active")
        self.active = True

    def is_active(self):
        return self.active

    def update_activity_tracking(self, ip_address):
        self.sign_in_count += 1
        self.last_sign_in_on = self.current_sign_in_on
        self.current_sign_in_on = datetime.datetime.now()


    def update_password(self, new_password):
        self.password = get_hashed_password(new_password)

    def __str__(self):
        user_attributes = vars(self)
        return (', '.join("%s: %s" % item for item in user_attributes.items()))

blueprints/user/views.py

from flask import Blueprint, render_template, request, jsonify
from prepsmarter.extensions import conn #database connection variable 

user = Blueprint('user', __name__, template_folder='templates')

@user.route('/register')
def login():
    return render_template('register.html')

@user.route('/new-user',methods = ['POST'])
def register_user():
   # where I am going to register a new user 

extensions/Database.py

import pymysql

class Database:
    instance = None 
    
    def __init__(self, host, user, password, db):
        self.host = host
        self.user = user
        self. password = password
        self.db = db 


    def connect(self):
        try: 
            conn = pymysql.connect(
                host = self.host,
                user = self.user,
                passwd = self.password,
                db = self.db
            )
            return conn
        except Exception as e:
            print(e)
\$\endgroup\$
1
  • \$\begingroup\$ Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. Please consider asking a new question instead. Feel free to add links in both questions towards another. \$\endgroup\$
    – Mast
    Feb 28, 2021 at 14:53

1 Answer 1

3
\$\begingroup\$

In general, your code seems to be in pretty good shape if you're hoping to move towards domain driven design, notably seen from your "entity-like" User object and the UserService.

Does it make sense to have2 separate classes? I am actually thinking to move all of my User methods to the UserService class and just have a UserService class with setters and getters.

First off, if you're anticipating your application to grow, a clear separation of concerns is what you'd want to achieve and to further achieve this, the two objects you have are fine. That is, one to represent a definition of what a user is and another to determine what you can do with a user object or related objects which in this case is the UserService. This naturally means that the methods on the User class should now be part of the UserService, considering them business logic of the application. I would use the words model to describe the User class and service to describe the UserService to define the types of components for the application.

I am confused about is where and when to interact with the database. Should I do it directly on my route.py vs create a new class?

One thing that you can also consider from domain driven design is also to include a repository object when your application becomes large enough. Repositories typically carry operations that might interact with a database, replacing what may be seen like an ORM that you have mentioned. Otherwise, if you're still small and nimble and don't have too many complex relationships in your database, you can keep it simple and keep the database queries in your model for simplicity for the time being. Both allows the user to be created from anywhere if needed and enables code re-use more than including them in the route. Designing from an interface perspective consider the following.

user_repository = UserRepository(db_conn)
user_service = UserService()
foo_user = user_service.register_user(...)

// using the repository
repository.save(foo_user)

// using the model
foo_user.save()

I'll end this with, it depends how large your application is growing, the larger it is, the more you want to re-evaluate your design so to better organize groups of functionality to better extend from and if possible re-use code. That said, what you may have could be good in the long run if your application doesn't really grow beyond those needs.

\$\endgroup\$
5
  • \$\begingroup\$ Thank you Will, this is a really great answer! I just moved all of my methods from the User to the UserService class. Since all of those methods require a User object, do I need to pass a User object in the UserService constructor or just have a User argument for eachUserService methods? \$\endgroup\$ Feb 28, 2021 at 10:51
  • \$\begingroup\$ I just figured out that in the constructor won't be possible since I do have a register_usermethod in the UserServiceclass now and the goal of this method is actually to create a user. \$\endgroup\$ Feb 28, 2021 at 11:00
  • \$\begingroup\$ I just updatd my post with the updated and new code :) \$\endgroup\$ Feb 28, 2021 at 11:09
  • \$\begingroup\$ @Pierre-Alexandre Generally, the UserService is on the business logic layer which generally means, it won't take a single user object as the constructor of the UserService. Rather, all the methods on the UserService instance probably takes a user object or creates a user object, for cases like updating, reference to be used in conjunction for other business logic. Let me know if that helps. \$\endgroup\$
    – Will C
    Mar 1, 2021 at 4:14
  • \$\begingroup\$ Yes, I did follow your suggestions and it's working \$\endgroup\$ Mar 4, 2021 at 18:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.