Introduction to Services in Django
This article introduces services in Django and explains how they can separate business workflows from views, forms, and models. It covers service functions, service classes, transactions, business rules, permissions, external APIs, background tasks, testing, project organization, and when a service layer is useful or unnecessary.
Introduction to Services in Django
As a Django application grows, views can easily become overloaded.
A small view may begin like this:
⧉
1 2 | |
Then more requirements are added:
- validate a cart
- calculate totals
- reserve inventory
- create an order
- charge a payment
- send confirmation email
- record analytics
- create an audit log
Soon the view contains a large amount of business logic.
This makes the code harder to:
- read
- test
- reuse
- maintain
- debug
- change safely
One common solution is to move application workflows into service functions or service classes.
A service layer is not a special Django feature. Django does not require one.
It is an application design pattern used to keep business operations separate from HTTP request handling.
A simple mental model is:
⧉
1 2 3 4 5 | |
The view handles the request and response.
The service handles the business operation.
What Is a Service?
A service is a function or class that performs an application-level operation.
For example:
⧉
1 2 | |
The service might:
- validate the cart
- calculate the total
- create the order
- create order items
- update inventory
- trigger a confirmation email
The view does not need to know every implementation detail.
It only needs to call:
⧉
1 2 3 4 | |
This makes the main action easier to understand.
Services Are Not Built Into Django
Django provides built-in concepts such as:
- models
- views
- forms
- middleware
- templates
- signals
There is no built-in:
⧉
1 | |
base class that every Django application must use.
A service layer is simply an organizational pattern.
You may create:
⧉
1 | |
or:
⧉
1 | |
inside an application.
For example:
⧉
1 2 3 4 5 6 7 8 | |
For a larger app:
⧉
1 2 3 4 5 6 7 8 | |
Why Use Services?
Services are most useful when an operation involves more than one simple model action.
Suppose a view contains:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
The HTTP behavior is mixed with the publishing workflow.
A service can separate them.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
The view becomes:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
The view now focuses on HTTP behavior.
Views and Services Have Different Responsibilities
A useful separation is:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
For example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
The view handles:
- request method
- form validation
- redirect
- template rendering
The service handles:
⧉
1 2 3 4 5 6 | |
Service Functions
A service does not need to be a class.
A plain function is often enough.
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
This is simple, explicit, and easy to call.
For many Django applications, service functions are easier to understand than introducing service classes immediately.
Keyword-Only Arguments
Service functions often use keyword-only arguments.
Example:
⧉
1 2 3 4 5 6 7 | |
The * means callers must write:
⧉
1 2 3 4 5 | |
instead of:
⧉
1 2 3 4 5 | |
This makes calls easier to read when a service takes several values.
Returning Results
A service should usually return something useful.
For example:
⧉
1 2 3 4 5 6 7 8 | |
The caller can then use:
⧉
1 2 3 4 5 | |
and redirect:
⧉
1 2 3 4 | |
Returning useful values keeps services composable.
Services and Models
Services do not replace models.
Models should still represent application data and model-specific behavior.
For example:
⧉
1 2 3 4 5 6 7 8 9 10 | |
A service might coordinate several model operations:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
The model owns behavior closely tied to its own state.
The service coordinates the wider workflow.
When Logic Belongs on the Model
Suppose an invoice can calculate its remaining amount:
⧉
1 2 3 4 5 6 | |
This is naturally model behavior.
The logic depends only on the invoice itself.
A service would add little value:
⧉
1 2 | |
That is unnecessary indirection.
Prefer model methods when behavior clearly belongs to one model instance.
When Logic Belongs in a Service
A service becomes useful when a workflow crosses boundaries.
For example:
⧉
1 2 3 4 5 6 7 8 9 | |
A checkout service might coordinate all of them.
⧉
1 2 | |
This operation does not belong naturally to just one model.
Services and Forms
Forms are responsible for validating input.
Services are responsible for performing business operations.
For example:
⧉
1 2 3 4 5 6 7 8 | |
The view:
⧉
1 2 3 4 5 6 7 8 | |
The form validates:
- the recipient value
- the decimal format
- the minimum amount
The service can enforce business rules:
⧉
1 2 3 4 5 6 7 8 9 10 | |
This distinction is important.
A user can call application logic from somewhere other than a form.
Critical business rules should not exist only in browser-facing validation.
Services and Business Rules
A service is a good place for rules that define how an operation works.
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
The service describes the business operation:
⧉
1 | |
rather than the HTTP request:
⧉
1 | |
This distinction makes the same operation reusable from:
- a web view
- an admin action
- a management command
- a background task
- an API endpoint
Services Improve Reuse
Without a service, two views may duplicate logic.
Web view:
⧉
1 2 3 4 5 6 | |
API view:
⧉
1 2 3 4 5 6 | |
This duplication can drift over time.
Instead:
⧉
1 2 3 4 5 6 | |
Both entry points call the same workflow.
Services and Transactions
Services are often a natural place for database transactions.
Suppose creating an order requires:
- creating the order
- creating order items
- decreasing inventory
If step three fails, the first two changes may need to roll back.
Use:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
Now the database operation is treated as one transaction.
If an exception occurs before completion, Django can roll back the database changes.
Transactions Do Not Roll Back Everything
A database transaction only controls database changes.
Suppose a service does this:
⧉
1 2 3 4 5 6 7 | |
The order may roll back.
The email cannot be “unsent.”
Likewise, transactions do not automatically undo:
- file uploads
- API requests
- payment charges
- messages sent to external queues
Be careful when combining database transactions with external side effects.
transaction.on_commit()
Sometimes an external action should occur only after a successful database commit.
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
If the database transaction rolls back, the callback is not run.
This is useful for:
- sending emails
- dispatching background tasks
- notifying external systems
when those actions depend on committed data.
Custom Service Exceptions
A service may need to report a business failure.
Instead of returning unclear values such as:
⧉
1 | |
define an exception:
⧉
1 2 | |
Service:
⧉
1 2 3 4 5 | |
The view can translate the business error into an HTTP response:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
This preserves the separation:
⧉
1 2 3 4 5 | |
Avoid Returning HttpResponse from Services
A service should usually not return:
⧉
1 | |
or:
⧉
1 | |
For example, avoid:
⧉
1 2 3 | |
That makes the service dependent on the web layer.
Prefer:
⧉
1 2 3 | |
Then the view decides:
⧉
1 2 3 4 5 6 7 8 9 | |
This makes the service easier to reuse outside views.
Avoid Passing the Whole Request to Services
Avoid:
⧉
1 2 3 | |
The service now depends directly on Django’s HTTP request object.
Prefer explicit inputs:
⧉
1 2 3 4 5 6 | |
Call it from the view:
⧉
1 2 3 4 5 6 | |
Explicit parameters make dependencies easier to understand and test.
Services Should Receive Useful Domain Objects
A service can accept:
- model instances
- IDs
- simple values
- dataclasses
- validated form values
For example:
⧉
1 2 3 4 5 6 7 | |
This is usually clearer than:
⧉
1 2 | |
The function signature documents what the operation actually needs.
Services and Permissions
There are two reasonable places for permission checks depending on the application.
A view can enforce access before calling the service:
⧉
1 2 3 4 5 6 7 8 9 | |
Or a service may enforce an important business authorization rule:
⧉
1 2 3 4 5 6 7 8 9 | |
For critical operations reused from many entry points, enforcing the rule inside the service can reduce the chance that one caller forgets it.
The HTTP view can still perform earlier access checks for a better user experience.
Services and QuerySets
A service may query models directly.
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
Or the caller may retrieve the object first:
⧉
1 2 3 4 5 6 7 8 | |
Neither style is universally correct.
A useful guideline is to make ownership of the lookup clear.
If the view needs special HTTP behavior such as 404, the lookup often belongs in the view.
If retrieving the object is inherently part of the business operation, the service may perform it.
Services and Selectors
Some projects separate read logic from write workflows.
They may use:
⧉
1 2 3 4 5 | |
Example selector:
⧉
1 2 3 4 5 6 7 8 9 | |
Service:
⧉
1 2 | |
This separation is optional.
For many applications, custom model managers and querysets already provide a good place for reusable query logic.
Custom QuerySets Versus Services
Consider:
⧉
1 | |
A custom queryset method is a natural place for reusable database filtering.
⧉
1 2 3 4 5 | |
A service function like:
⧉
1 2 3 4 | |
may add little value unless the query represents a more complex application operation.
Use the abstraction that keeps the code clearest.
Service Classes
Sometimes a service class can be useful.
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
Call:
⧉
1 2 3 4 5 6 | |
A class can be useful when:
- several methods share state
- the workflow has multiple meaningful stages
- dependencies need to be injected
- the object itself represents a workflow
However, do not create a class merely because the file is called services.py.
This:
⧉
1 2 3 4 | |
may be less clear than:
⧉
1 2 | |
Start with functions unless a class provides a real advantage.
Avoid Generic Service Base Classes
This is usually unnecessary:
⧉
1 2 | |
followed by:
⧉
1 2 | |
Django does not require services to share a common base class.
A generic hierarchy can add complexity without improving the application.
Prefer concrete operations with clear names.
Name Services After Actions
Good service names describe business actions.
Examples:
⧉
1 2 3 4 5 6 7 | |
Less useful names include:
⧉
1 2 3 4 | |
A reader should understand the operation from the function name.
Organizing services.py
For a small application:
⧉
1 2 3 4 | |
services.py:
⧉
1 2 3 4 5 6 7 8 9 10 | |
This is often enough.
Organizing a Services Package
When services.py becomes too large:
⧉
1 2 3 4 5 6 7 8 | |
Imports can remain clear:
⧉
1 2 3 | |
Do not split service files too early.
A single clear file is easier to navigate than many nearly empty files.
Services Across Django Apps
Suppose an order service needs:
⧉
1 2 3 | |
It is normal for an application-level service to coordinate models from several apps when the workflow itself belongs to the orders domain.
Example:
⧉
1 2 | |
The most important question is:
⧉
1 | |
not:
⧉
1 | |
External APIs
Services are often a good place to coordinate external systems.
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
The view should not need to understand the payment provider’s implementation details.
Keep Third-Party Integrations Behind Boundaries
Instead of spreading:
⧉
1 | |
through views, models, and management commands, wrap external behavior behind application-specific functions.
For example:
⧉
1 2 3 4 5 6 | |
Then the order service uses:
⧉
1 2 3 4 | |
This makes third-party systems easier to replace and mock in tests.
Services and Background Tasks
A service may dispatch slow work to a background queue.
For example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
This keeps long-running tasks out of the HTTP request when appropriate.
The service still describes the business workflow.
Avoid Hiding Too Much
Services should make the application easier to understand.
They should not make ordinary operations mysterious.
For example:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
This may be useful if comment creation is a real application operation.
But if the function adds no behavior and has only one caller, this may simply add another layer to navigate.
Not every:
⧉
1 | |
needs a service.
Do Not Create Services for Every CRUD Operation
Avoid mechanically creating:
⧉
1 2 3 4 | |
just because CRUD exists.
Django already provides:
- managers
- querysets
- forms
- generic views
- model methods
A service is valuable when it represents meaningful application behavior.
For example:
⧉
1 | |
communicates more business meaning than:
⧉
1 | |
Services Should Not Become God Objects
Avoid a single class like:
⧉
1 2 | |
with hundreds of unrelated methods.
Likewise, avoid a huge:
⧉
1 | |
that contains every workflow in the project.
Group services by application or business area.
For example:
⧉
1 2 3 | |
Keep Services Focused
A service should usually describe one meaningful operation.
Example:
⧉
1 2 | |
not:
⧉
1 2 3 4 5 6 7 8 9 10 | |
Large functions controlled by flags are often harder to understand than several focused operations.
Services and Signals
Suppose an order should send an email after creation.
One approach is a post_save signal.
Another is explicit service logic:
⧉
1 2 3 4 5 6 | |
The service makes the relationship explicit.
A reader can see immediately that creating an order triggers the email.
Signals may still be useful for loosely coupled notifications, but important business workflows are often easier to follow when coordinated explicitly by a service.
Services and Model save()
Avoid turning every model save into a hidden business workflow.
For example:
⧉
1 2 3 4 5 6 7 | |
This means a seemingly simple:
⧉
1 | |
may trigger several external effects.
A service makes the operation clearer:
⧉
1 2 3 | |
Explicit operations are easier to reason about.
Testing Services
Services are usually straightforward to test because they receive explicit inputs and return useful results.
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
The test does not need to simulate an HTTP request unless HTTP behavior is part of what is being tested.
Testing Business Rules
Suppose an order cannot be cancelled after shipment.
Service:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Test:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
This test directly describes the business rule.
Mocking External Dependencies
Suppose a service calls a payment provider.
⧉
1 2 3 4 5 6 7 8 9 | |
Test without calling the real provider:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
Service boundaries often make external integrations easier to replace during tests.
Integration Tests Still Matter
Testing a service in isolation is useful, but the application should also test that views call it correctly.
For example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
A healthy test suite can contain both:
⧉
1 2 3 4 5 | |
A Complete Example
Consider a small order system.
Models:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | |
Custom exception:
⧉
1 2 | |
Service:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | |
View:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | |
The responsibilities are now clear.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Another Complete Example: User Registration
Registration often contains more than:
⧉
1 | |
Suppose registration also creates a profile and sends a welcome email.
Service:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | |
View:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | |
This registration operation can now also be reused from another entry point without duplicating the workflow.
Common Beginner Mistakes
Creating a Service Layer Too Early
Not every application needs services from the beginning.
This is perfectly reasonable:
⧉
1 2 | |
if the operation is simple.
Introduce a service when it improves clarity or reuse.
Moving All Code Out of Views
A service layer does not mean views should contain no logic.
Views still need to handle:
- request methods
- forms
- authentication
- permissions
- redirects
- response rendering
The goal is separation, not empty views at all costs.
Moving Model Behavior Into Services
Avoid:
⧉
1 2 | |
when:
⧉
1 | |
already expresses the operation.
Similarly, model-specific behavior may belong on the model.
Passing request Everywhere
Avoid:
⧉
1 2 | |
Prefer:
⧉
1 2 3 4 5 6 | |
The service should receive the data it actually needs.
Returning HTTP Responses from Services
Avoid:
⧉
1 | |
inside a business service.
Return application data or raise a meaningful exception.
Swallowing Errors
Avoid:
⧉
1 2 3 4 5 | |
This hides why the operation failed.
Catch only errors the service can meaningfully handle.
Creating Huge Service Classes
A service layer should reduce complexity, not create a new giant abstraction.
Prefer focused operations.
Duplicating Model Managers
Do not introduce a service function for every simple queryset.
Use custom managers and querysets where they naturally fit.
Using Services Only as Wrappers
This:
⧉
1 2 3 4 | |
may not provide enough value unless it establishes an important application boundary or is expected to gain real workflow behavior.
Mixing HTTP and Business Exceptions
A service should usually raise business-oriented exceptions.
For example:
⧉
1 | |
rather than returning:
⧉
1 | |
The view can map the business failure to HTTP.
Forgetting Transactions
When a workflow performs several related writes, think about what should happen if one of them fails.
A transaction may be required.
Assuming Transactions Cover External Systems
Database rollback cannot undo an already-sent email or payment request.
Coordinate side effects deliberately.
When to Use a Service
A service is often useful when an operation:
- touches several models
- contains important business rules
- is used from several entry points
- needs a database transaction
- calls an external system
- has meaningful failure states
- is difficult to test through a view
- makes a view too large
- represents a named business action
Examples:
⧉
1 2 3 4 5 6 7 8 | |
When a Service May Be Unnecessary
A service may add little value for:
- one simple model lookup
- basic CRUD handled cleanly by a
ModelForm - straightforward generic views
- a small model method
- a reusable queryset filter
For example:
⧉
1 2 3 4 | |
does not normally require:
⧉
1 | |
Use services because they clarify application behavior, not because every Django project must contain them.
Recommended Service Style
For many Django projects, a useful default is:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
A Simple Project Structure
For a small project:
⧉
1 2 3 4 5 6 7 8 9 | |
For a larger project:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Organize the code only as much as the application actually needs.
Recommended Mental Model
A useful way to think about a Django application is:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Not every request needs every layer.
For example, a simple read-only page might be:
⧉
1 2 3 4 5 | |
A complex checkout might be:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Use the layers that make the specific workflow easier to understand.
Mini Reference
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
Basic service:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Basic view usage:
⧉
1 2 3 4 5 6 7 8 9 | |
Transactional service:
⧉
1 2 3 | |
External action after commit:
⧉
1 2 3 | |
Services in Django provide a way to separate application workflows from HTTP request handling.
They are not a required Django feature and should not be added mechanically to every project.
Their main purpose is to make important business operations explicit.
A useful division of responsibilities is:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
Services become especially valuable when an operation touches several models, contains important business rules, needs transactions, calls external systems, or must be reused from several entry points.
Start with simple functions such as:
⧉
1 2 3 | |
Keep their inputs explicit, return useful results, and avoid coupling them to request or HttpResponse.
The goal is not to create more layers.
The goal is to make the code easier to understand.
When a developer opens a view and sees:
⧉
1 2 3 4 | |
the important business action is immediately visible.
That clarity is the main reason a service layer can be useful in a growing Django application.
Join the Newsletter
Practical insights on Django, backend systems, deployment, architecture, and real-world development — delivered without noise.
Get updates when new guides, learning paths, cheat sheets, and field notes are published.
No spam. Unsubscribe anytime.
There is no third-party involved so don't worry - we won't share your details with anyone.