paint-brush
Templating in Software Development: Taking a Deeper Lookby@pro1code1hack
427 reads
427 reads

Templating in Software Development: Taking a Deeper Look

by Yehor DremliuhaJuly 16th, 2024
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

Many core functionalities are reused across different projects. These functionalities include user authentication, payment processing, user management, and more. In this article I would like to raise the point that all of these patterns were already created by programmers from the past. Almost everything we are using right now, was already implemented. We just modify some functionality based on the specific project.
featured image - Templating in Software Development: Taking a Deeper Look
Yehor Dremliuha HackerNoon profile picture

1. Introduction

Since I started my journey with programming, I noticed an interesting pattern: the majority of applications are templated. Yes, it’s a pure fact! Just stop right here, at the beginning of this article, and start thinking of all the projects you have developed.


What do they have in common? If you look closely, you’ll see that many core functionalities are reused across different projects. These core functionalities often include user authentication, payment processing, user management, and more.


In this article, I would like to raise the point that all of these patterns were already created by programmers from the past.Really, almost everything we are using right now, was already implemented. We just modify some functionality based on the specific project.


I will introduce you to examples from the backend development perspective in Python, but this can be applied to any programming language or any field in the realm of software engineering.


So, what do all backend applications have in common? Let’s take a look!


Note: If you are familiar with OOP (Object Oriented Programming), consider your templated modules as the highest level of abstraction but on the application level so that this should be written according to this principle.

2. Authentication and Authorization

I would like to break down each further section to the basic components, which can be applied almost to any backend application.


Basic Components

  1. User Model: A representation of the user that includes attributes like username, password, email, roles, etc.
  2. Password Management: Functions to hash and verify passwords.
  3. Token Generation: Mechanism to generate and verify tokens (JWT, OAuth2, etc.).
  4. Middleware/Decorator: Protect routes/endpoints that require authentication.
  5. Role Management: Assign and verify user roles and permissions.


2.1 User Model

We are defining the most generic class of the User with attributes that can be applied to any specific user.

from werkzeug.security import generate_password_hash, check_password_hash

class User:
    def __init__(self, username, password, email):
        self.username = username
        self.password_hash = generate_password_hash(password)
        self.email = email
        self.roles = []

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)


2.2 Token Management

Using JWT for token-based authentication is a good choice in terms of cybersecurity and best practices in backend development.

def generate_token(user):
    payload = {
        'username': user.username,
        'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
    }
    return jwt.encode(payload, SECRET_KEY, algorithm='HS256')

def verify_token(token):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
        return payload['username']
    except jwt.ExpiredSignatureError:
        return None
    except jwt.InvalidTokenError:
        return None


2.3 Decorators

  • Decorator to check if the user is allowed to access the page.
from functools import wraps
from flask import request, jsonify, session

def is_authenticated(func):
    @wraps(func)
    def decorated_function(*args, **kwargs):
        if 'user' not in session:
            return jsonify({"error": "User not authenticated"}), 401
        return func(*args, **kwargs)
    return decorated_function


  • Decorator to check the role of the user.
def roles_required(*roles):
    def decorator(func):
        @wraps(func)
        def decorated_function(*args, **kwargs):
            user_roles = session.get('roles', [])
            if not any(role in user_roles for role in roles):
                return jsonify({"error": "User does not have the required role"}), 403
            return func(*args, **kwargs)
        return decorated_function
    return decorator


And basically, that’s it! You can use this predefined functionality for authentication across all projects!

3. Payments and Billing

Almost any application handles financial transactions. Whether it’s a local butcher shop or a big enterprise giant, you will need to use an effective system to gather payments.


Basic Components

  1. Payment Gateway Integration: Connecting with payment gateways like Stripe or PayPal
  2. Payment Models: Defining models to handle payment data.
  3. Payment Processing: Handling the payment lifecycle (initiate, confirm, etc.).


3.1 Payment Gateway Integration

This can be used as a base for integrating different payment gateways, with a concrete implementation for Stripe. Generally, my personal preference is to use StripeAPI for payments as it has been for a long time on the market, and really, easy to integrate into any project.


class PaymentGateway(ABC):
    
    @abstractmethod
    def create_payment_intent(self, amount, currency='gbp'):
        pass

    @abstractmethod
    def confirm_payment(self, payment_id):
        pass

    @abstractmethod
    def handle_webhook(self, payload, sig_header):
        pass

This is the most generic example for the payment gateway, and you can focus on specific implementation according to your needs.


3.2 Payment Models

Define models to store payment information. This example can be adapted for use with ORM. You may create a more complex hierarchy of classes if needed, but for this example, the following snippet should be pretty sufficient.

class Payment:
    def __init__(self, user_id, amount, currency):
        self.id = uuid.uuid4()
        self.user_id = user_id
        self.amount = amount
        self.currency = currency
        self.status = status

payments = []


Save all payments into the database and set up a Celery task for processing transactions, for the 3.3 section. The database records should look like the following:

                  id                  |              user_id              | amount | currency |  status  
--------------------------------------+-----------------------------------+--------+----------+----------
 e532d653-7c8b-453a-8cd4-3ab956863d72 | 1ff9efb3-d5e8-4e53-854f-4246ba9ff638 | 100.00 | USD      | Failed
 35985d41-5d54-4021-bed6-82d7233cc353 | a0984002-bace-478e-b6f9-6e4459e1b5ba | 250.50 | EUR      | Pending
 1ff9efb3-d5e8-4e53-854f-4246ba9ff638 | 9f896874-dc43-4592-8289-d0f7f8b8583a |  99.99 | GBP      | Completed


Now, we have created a complex system that can be integrated into any project. Are you still following the pattern? This can be used EVERYWHERE!


After all, you can define another application for visualization of this data. You’ve got the point about templating! 😉

4. Email and Notification Services

Email and notifications keep users informed and engaged in the life of your app. Whether it's for account verification, password resets, or marketing communications, a reliable email service is essential for any type of project.

  1. Email Service Integration: Connecting with email services like SendGrid or Amazon SES.
  2. Email Templates: Defining templates for various email types.
  3. Sending Emails: Functions to send emails using the integrated service.


4.1 Email Service Integration

Define the main logic of SendGrid for sending emails inside EmailService class.

import sendgrid
from sendgrid.helpers.mail import Mail

class EmailService:
    def __init__(self, api_key):
        self.sg = sendgrid.SendGridAPIClient(api_key)

    def send_email(self, from_email, to_email, subject, html_content):
        email = Mail(
            from_email=from_email,
            to_emails=to_email,
            subject=subject,
            html_content=html_content
        )
        try:
            response = self.sg.send(email)
            return response.status_code
        except Exception as e:
            return str(e)


As with payment gateway, you don’t need to focus on any specific utils or products on the market. This is just an example of how it can be generalized and templated for any project.


4.2 Email Templates

My favorite pattern for systems like this is the handlers pattern; you just add more and more keys to the dictionary as a type of email, and the path to the file with a content.


email_templates = { 
'welcome': “welcome.html”, 
'reset_password': "<h1>Reset Your Password</h1><p>Click <a href='{link}'>here</a> to reset your password.</p>" 
}


Otherwise, it could be nice to define an Enum for the same purposes.


4.3 Sending Emails

We need a function to make the magic happen! Let’s write the following:

def send_email(email_service, from_email, to_email, subject, template_name, **template_vars):
    """
    Send an email using the specified email service.
    """
    html_content = get_email_template(template_name, **template_vars)
    return email_service.send_email(from_email, to_email, subject, html_content)


Another important point would be adding several files to all backend projects, such as README .env config.py, pyproject.toml, .pre-commit.yml and come up with the base structure of the files inside the project.


Though the suggested structure is a little bit tightened to Python implementation, as I mentioned before, you can do the same for any other language or field.


It is also important to note that templating at the highest level of abstraction and maintaining a good structure of the application can be

reused for other projects as a package or an addition to the microservice architecture.



Figure 1 - Microservice Architecture Example

5. Conclusion

EVERYTHING CAN BE TEMPLATED!


The examples provided here are just the beginning—these patterns can be extended and refined to cover more complex scenarios as your projects evolve. You may add caching establish k8s, docker, Devops infrastructure, CI/CD and pipelines.


Remember one simple statement: Once you have done your job properly, you can use the same job while completing another.


The goal is to make the project, infrastructure, components, and services reusable across different applications!


Make yourself a cup of tea, and think about which parts of your applications can be re-used in different apps. Try to create similar services and automate your job, adjusting only some code pieces!


Thanks for reading, and happy templating!