Post

Service Oriented Architecture (SOA): Principles and Best Practices

A comprehensive guide to understanding Service Oriented Architecture, its principles, benefits, challenges, and implementation best practices.

Service Oriented Architecture (SOA): Principles and Best Practices

Controller Responsibilities & Service Pattern


In Laravel, controllers are responsible for handling requests and responses. However, to maintain clean and testable code, we should separate concerns:

  • Business logic should be placed in a service class.
  • Data access should be handled in a repository (if needed).

Why Use the Service Pattern?

Using the Service Pattern provides the following benefits:

Code Maintainability – Business logic is kept separate from controllers, making updates easier.

Reusability – The same service logic can be used across multiple controllers or commands.

Testability – Services can be unit-tested independently from the framework.


How to Implement the Service Pattern in Laravel

1- Generate a Service Class

Create a UserService class using the following command:

1
php artisan make:class Services/UserService

2- Define the Service Logic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace App\Services;

use App\Models\User;

class UserService {
    public function create(array $userData): User {
        $user = User::create($userData);
        $user->roles()->sync($userData['roles']);
        return $user;
    }

    public function update(array $userData, User $user): User {
        $user->update($userData);
        $user->roles()->sync($userData['roles']);
        return $user;
    }
}

📌 Notes:

🔹 Services handle business logic, keeping controllers thin and clean.

🔹 We use sync() to update user roles efficiently.

🔹 Dependency injection is recommended to instantiate services.


3- Use the Service in a Controller

There are three ways to inject a service into a controller:

  1. Instantiating Directly (❌ Not Recommended – tight coupling)
  2. Method Injection (✅ Preferred – flexible and explicit)
  3. Constructor Injection (✅ Best for dependencies used across multiple methods)

Example: Using Method Injection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use App\Services\UserService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;

class UserController extends Controller {
    public function store(Request $request, UserService $userService) {
        $userService->create($request->validated());
        return redirect()->route('users.index');
    }

    public function update(Request $request, User $user, UserService $userService) {
        $userService->update($request->validated(), $user);
        return redirect()->route('users.index');
    }
}

Example: Using Constructor Injection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use App\Services\UserService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;

class UserController extends Controller {
    private UserService $userService;

    public function __construct(UserService $userService) {
        $this->userService = $userService;
    }

    public function store(Request $request) {
        $this->userService->create($request->validated());
        return redirect()->route('users.index');
    }

    public function update(Request $request, User $user) {
        $this->userService->update($request->validated(), $user);
        return redirect()->route('users.index');
    }
}

If your service class requires constructor parameters, bind it in a Service Provider:

1
2
3
$this->app->bind(UserService::class, function () {
    return new UserService(*/* dependencies */*);
});

This ensures Laravel resolves the service correctly when injected.

📌 Notes:

Constructor Injection is ideal when a service is used in multiple methods within the controller.

✅ Laravel’s service container automatically resolves the dependency when the controller is instantiated.

✅ This ensures the service is always available and reduces method parameters.


Advanced approach (Kind of striction )

in service folder create two folders

  • contract folder (that has interface that has all function that should be exist in service )
  • services folder (that has all service that implements interfaces in contract folder )
1
2
3
4
5
6
app/
└── Services/
    ├── Contracts/
    │   └── UserServiceInterface.php
    └── Services/
        └── UserService.php

1. Create the Contract Interface

app/Services/Contracts/UserServiceInterface.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
php
CopyEdit
<?php

namespace App\Services\Contracts;

use App\Models\User;

interface UserServiceInterface
{
    public function create(array $data): User;
    public function update(User $user, array $data): bool;
    public function delete(User $user): bool;
}

2. Create the Service Class

app/Services/UserService.php

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
php
CopyEdit
<?php

namespace App\Services;

use App\Models\User;
use App\Services\Contracts\UserServiceInterface;
use Illuminate\Support\Facades\Hash;

class UserService implements UserServiceInterface
{
    public function create(array $data): User
    {
        $data['password'] = Hash::make($data['password']);
        return User::create($data);
    }

    public function update(User $user, array $data): bool
    {
        if (isset($data['password'])) {
            $data['password'] = Hash::make($data['password']);
        }
        return $user->update($data);
    }

    public function delete(User $user): bool
    {
        return $user->delete();
    }
}


3. Bind Interface to Implementation in a Service Provider

AppServiceProvider.php or create a dedicated one.

1
2
3
4
5
6
7
8
9
10
php
CopyEdit
use App\Services\Contracts\UserServiceInterface;
use App\Services\UserService;

public function register(): void
{
    $this->app->bind(UserServiceInterface::class, UserService::class);
}

4. Use the Service in a Controller

app/Http/Controllers/UserController.php

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
php
CopyEdit
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use App\Services\Contracts\UserServiceInterface;

class UserController extends Controller
{
    protected UserServiceInterface $userService;

    public function __construct(UserServiceInterface $userService)
    {
        this->userService = $userService;
    }

    public function store(Request $request)
    {
        $user = $this->userService->create($request->all());
        return response()->json($user);
    }

    public function update(Request $request, User $user)
    {
        $this->userService->update($user, $request->all());
        return response()->json(['message' => 'User updated']);
    }

    public function destroy(User $user)
    {
        $this->userService->delete($user);
        return response()->json(['message' => 'User deleted']);
    }
}

This post is licensed under CC BY 4.0 by the author.