🚧

Do not get confused!

Services might be different than the very first thing that comes to mind. People might get confused about it. It is just a concept that is a little bit different in glim. It's not about web service building.

In glim framework, services can be used for seperating models from logic. Moreover, you can hold class wrappings in services such as mail sending, message queueing, etc. They can be defined as the reusable components that controllers can use. In a typical glim app, services reside in app/services.py file.

📘

Completely Optional

This layer is completely optional. You may want to move database logic through controllers or models. However, services are a good place to hold db logic and some wrappings of libraries.

The database logic can be seperated into number of functions in services. Consider an example of a user service. You can define a class namely "UserService" resides in app/services.py;

# services.py
from glim_extensions.db import Orm
from app.models import User

class UserService:
    @staticmethod
  	def auth(email, password):
        user = Orm.query(User.id, User.email)
    							.filter_by(email=email, password=password)
    		return user

📘

A Note on static functions

Since the services are mostly stateless. Therefore, it is clever to make the service functions as static. However, if you are hesitant about using static, you can use classmethod or just functions with no classes. The following example shows of a service just by defining functions;

# services.py
from glim_extensions.db import Orm

def auth_user(email, password):
    user = Orm.query(User.id, User.email)
    				  .filter_by(email=email, password=password)
  	return user

Where to use services and where not to ?

Since it's completely optional to make use of services, there are some guidelines about where to use services and where not to.

Where to use services ?

  • Reusable functions for controllers

    Glim approach does not recommend reusability in the controller layer, meaning that if you
    are calling reusable functions inside controllers, these functions should be on the services
    layer. Glim recommends to move the logic to services. If you have different routes doing
    different jobs, these controllers should be mapped into seperate functions.

    Consider a web project having secured and non-secured requests. Suppose that clients
    can only see blog posts if they are authenticated. Moreover, they can only create blog
    posts if they are authenticated. A bad example of implementing this would be the
    following;

# routes.py
urls = {
    '/login': 'AuthController.login',
    '/post/create': 'PostController.create',
    '/post/:<int:id>': 'PostController.get'
}

# controllers.py
from glim import Controller
from glim_extensions.db import Orm

class AuthController(Controller):
    def login(self):
        # perform login using Orm usage
    def check_auth(self):
        # perform auth checking using Orm usage

class PostController(Controller):
    def create(self):
        auth_controller = AuthController(self.request)
        if auth_controller.check_auth():
            # perform post creating
        else:
            # send unauthorized response

    def get(self, id):
        auth_controller = AuthController(self.request)
        if auth_controller.check_auth():
            # perform post getting
        else: 
            # send unauthorized response

You might notice of a little bit dirty code here. It is, actually not clean. Instead of instantiating AuthController, you can move this to the service layer;

# routes.py
urls = {
    '/login': 'AuthController.login',
    '/post/create': 'PostController.create',
    '/post/<int:id>': 'PostController.get'
}

# services.py
from glim import Service

class AuthService(Service):
    @staticmethod
    def check(self):
        # if user is auth, then return True
        # else, return False

# controllers.py
from glim.component import Controller
from glim.db import Orm
from app.services import AuthService

class AuthController(Controller):
    def login(self):
        # perform login using Orm usage

class PostController(Controller):
    def create(self):
        if AuthService.check():
            # perform post creating
        else:
            # send unauthorized response

    def get(self, id):
        if AuthService.check():
            # perform post getting
        else: 
            # send unauthorized response

Controllers should only dealing with requests and responses. It will be a little bit cleaner this way. This can be thought as angular.js's services.

📘

The best way of auth checking would be in filters

The above example is also not a good practice. Use filters for auth checking instead!

  • Wrapping a class w/out any db operation

    In glim, services are a good place to wrap classes and could be made static for easy to be
    called from outside. If you consider an example of a mail sending logic, you would put this
    logic inside services

    Consider an example of a system having approval mails after a successful registration.
    This app would be the following;

# routes.py
urls = {
    '/user/register': 'UserController.register',
    '/user/approve': 'UserController.approve'
}

# services.py
from glim import Service
class MailService(Service):
    @staticmethod
    def send_approval_mail(from, to, body):
        # perform mail sending
        # return if mail is sent

# controllers.py
from glim import Controller
from app.services import MailService

class UserController(Controller):
    def register(self):
        # perform registering using services or just here
        from = 'blah'
        to = 'blah'
        body = 'Welcome to glim my friend!'
        result = MailService.send_approval_mail(from, to, body)
        # return appropriate response
    def approve(self):
        # perform approval using services or just here
        # return appropriate response

In this example, the mail service is used as a wrapper for a mail library. Therefore, as you might notice, services can be pretty useful when it comes to library wrapping.

Where not to use services ?

  • For simplicity, if you have not much reusable database logic.
    Services can be pretty complex and useless when every single controller has its own
    service. Therefore, you may want to move the database logic if it's not reusable in other
    controllers. This can be done on small apps or even bigger apps. Services are for keeping
    reusability in controller layer without creating functions inside controllers that are not
    mapped to routes.

    However, if you need library mapping, services would still be the greatest place to hold that.

📘

Again not a must, but recommended

These are just recommended practices glim framework is offering, you can make it different as much as you want.