Service classes are a popular way to extract application logic and keep your code DRY. The best way to learn is to look at practical examples. In this post, we'll examine ten examples from Laravel open-source projects and how they use Services differently.
The article is very long, so here's the Table of Contents:
execute()
: Similar to Queueable JobAll the examples include links to GitHub repositories so you can explore them further. Let's go!
The first example is from an open-source project called pterodactyl/panel. Here, we have a UserCreationService
service, which creates a user using the handle()
method.
app/Services/Users/UserCreationService.php:
class UserCreationService{ public function handle(array $data): User { if (array_key_exists('password', $data) && !empty($data['password'])) { $data['password'] = $this->hasher->make($data['password']); } $this->connection->beginTransaction(); if (!isset($data['password']) || empty($data['password'])) { $generateResetToken = true; $data['password'] = $this->hasher->make(str_random(30)); } /** @var \Pterodactyl\Models\User $user */ $user = $this->repository->create(array_merge($data, [ 'uuid' => Uuid::uuid4()->toString(), ]), true, true); if (isset($generateResetToken)) { $token = $this->passwordBroker->createToken($user); } $this->connection->commit(); $user->notify(new AccountCreated($user, $token ?? null)); return $user; }}
This service method is called in a few places: two Controllers and one Artisan command.
Reusability is one of the benefits of Services.
app/Http/Controllers/Admin/UserController.php:
// ... use Pterodactyl\Services\Users\UserCreationService; class UserController extends Controller{ public function __construct( protected AlertsMessageBag $alert, protected UserCreationService $creationService, // ... ) { } public function store(NewUserFormRequest $request): RedirectResponse { $user = $this->creationService->handle($request->normalize()); $this->alert->success($this->translator->get('admin/user.notices.account_created'))->flash(); return redirect()->route('admin.users.view', $user->id); } // ...}
app/Console/Commands/User/MakeUserCommand.php:
use Pterodactyl\Services\Users\UserCreationService; class MakeUserCommand extends Command{ // ... public function __construct(private UserCreationService $creationService) { parent::__construct(); } public function handle() { // ... $user = $this->creationService->handle(compact('email', 'username', 'name_first', 'name_last', 'password', 'root_admin')); // ... }}
This example comes from the serversideup/financial-freedom open-source project.
The primary Service method is simple: just getting data from the database.
app/Services/CreditCards/IndexCreditCards.php:
use App\Models\CreditCard; class IndexCreditCards{ public function index() { $creditCards = CreditCard::with('institution') ->with('rules') ->where('user_id', auth()->id()) ->orderBy('name', 'ASC') ->get(); return $creditCards; }}
The benefit is that it is used in multiple Controllers.
app/Http/Controllers/TransactionController.php:
use App\Services\CreditCards\IndexCreditCards; // ... class TransactionController extends Controller{ public function index( Request $request ): Response { return Inertia::render( 'Transactions/Index', [ 'group' => 'transactions', 'transactions' => fn () => ( new IndexTransactions() )->execute( $request ), 'groups' => fn () => ( new IndexGroups() )->index( $request ), 'cashAccounts' => fn() => ( new IndexCashAccounts() )->index(), 'creditCards' => fn() => ( new IndexCreditCards() )->index(), 'loans' => fn() => ( new IndexLoans() )->index(), 'filters' => $request->all() ] ); } // ...}
app/Http/Controllers/AccountController.php:
use App\Services\CreditCards\IndexCreditCards; // ... class AccountController extends Controller{ public function index( Request $request ): Response { return Inertia::render('Accounts/Index', [ 'group' => 'accounts', 'cashAccounts' => fn() => ( new IndexCashAccounts() )->index(), 'creditCards' => fn() => ( new IndexCreditCards() )->index(), 'loans' => fn() => ( new IndexLoans() )->index(), 'institutions' => fn () => ( Institution::orderBy('name', 'ASC')->get() ), ]); } // ...}
In this example, we have the Bottelet/DaybydayCRM open-source project.
The first two examples were more like Action classes of doing one thing. But a more typical case is a Service class with multiple methods working with the same object or topic, like invoices, in this case.
The InvoiceCalculator
service has six methods for invoice calculations.
It's one of the differences between the Service and Action classes: multiple methods instead of just a single handle()
or similar. It's one of the most popular lessons in our course How to Structure Laravel Projects.
app/Services/Invoice/InvoiceCalculator.php:
use App\Models\Offer;use App\Models\Invoice;use App\Repositories\Tax\Tax;use App\Repositories\Money\Money; class InvoiceCalculator{ private $invoice; private $tax; public function __construct($invoice) { if(!$invoice instanceof Invoice && !$invoice instanceof Offer ) { throw new \Exception("Not correct type for Invoice Calculator"); } $this->tax = new Tax(); $this->invoice = $invoice; } public function getVatTotal() { $price = $this->getSubTotal()->getAmount(); return new Money($price * $this->tax->vatRate()); } public function getTotalPrice(): Money { $price = 0; $invoiceLines = $this->invoice->invoiceLines; foreach ($invoiceLines as $invoiceLine) { $price += $invoiceLine->quantity * $invoiceLine->price; } return new Money($price); } public function getSubTotal(): Money { $price = 0; $invoiceLines = $this->invoice->invoiceLines; foreach ($invoiceLines as $invoiceLine) { $price += $invoiceLine->quantity * $invoiceLine->price; } return new Money($price / $this->tax->multipleVatRate()); } public function getAmountDue() { return new Money($this->getTotalPrice()->getAmount() - $this->invoice->payments()->sum('amount')); } public function getInvoice() { return $this->invoice; } public function getTax() { return $this->tax; }}
For example, InvoicesController
uses the Service to get various invoice information.
app/Http/Controllers/InvoicesController.php:
use App\Models\Invoice;use App\Services\Invoice\InvoiceCalculator; class InvoicesController extends Controller{ // ... public function show(Invoice $invoice) { // ... $invoiceCalculator = new InvoiceCalculator($invoice); $totalPrice = $invoiceCalculator->getTotalPrice(); $subPrice = $invoiceCalculator->getSubTotal(); $vatPrice = $invoiceCalculator->getVatTotal(); $amountDue = $invoiceCalculator->getAmountDue(); // ... } // ...}
In this example, we have an open-source project named officelifehq/officelife.
This Service has an execute()
method and four private methods where all the logic is done. All the validation rules have also been added to this service.
app/Services/Company/Project/CreateProjectLink.php:
use Carbon\Carbon;use App\Jobs\LogAccountAudit;use App\Services\BaseService;use App\Models\Company\Project;use Illuminate\Validation\Rule;use App\Models\Company\ProjectLink;use App\Models\Company\ProjectMemberActivity; class CreateProjectLink extends BaseService{ protected array $data; protected ProjectLink $projectLink; protected Project $project; public function rules(): array { return [ 'company_id' => 'required|integer|exists:companies,id', 'author_id' => 'required|integer|exists:employees,id', 'project_id' => 'required|integer|exists:projects,id', 'type' => [ 'required', Rule::in([ 'slack', 'email', 'url', ]), ], 'label' => 'nullable|string|max:255', 'url' => 'required|string|max:255', ]; } public function execute(array $data): ProjectLink { $this->data = $data; $this->validate(); $this->createLink(); $this->logActivity(); $this->log(); return $this->projectLink; } private function validate(): void { $this->validateRules($this->data); $this->author($this->data['author_id']) ->inCompany($this->data['company_id']) ->asNormalUser() ->canExecuteService(); $this->project = Project::where('company_id', $this->data['company_id']) ->findOrFail($this->data['project_id']); } private function createLink(): void { $this->projectLink = ProjectLink::create([ 'project_id' => $this->data['project_id'], 'label' => $this->valueOrNull($this->data, 'label'), 'type' => $this->data['type'], 'url' => $this->data['url'], ]); } private function logActivity(): void { ProjectMemberActivity::create([ 'project_id' => $this->project->id, 'employee_id' => $this->author->id, ]); } private function log(): void { LogAccountAudit::dispatch([ 'company_id' => $this->data['company_id'], 'action' => 'project_link_created', 'author_id' => $this->author->id, 'author_name' => $this->author->name, 'audited_at' => Carbon::now(), 'objects' => json_encode([ 'project_link_id' => $this->projectLink->id, 'project_link_name' => $this->projectLink->label, 'project_id' => $this->project->id, 'project_name' => $this->project->name, ]), ])->onQueue('low'); }}
This service is used in several places, one of which is ProjectController
.
app/Http/Controllers/Company/Company/Project/ProjectController.php:
use App\Services\Company\Project\CreateProjectLink; // ... class ProjectController extends Controller{ // ... public function createLink(Request $request, int $companyId, int $projectId): JsonResponse { $loggedEmployee = InstanceHelper::getLoggedEmployee(); $company = InstanceHelper::getLoggedCompany(); $data = [ 'company_id' => $company->id, 'author_id' => $loggedEmployee->id, 'project_id' => $projectId, 'type' => $request->input('type'), 'label' => ($request->input('label')) ? $request->input('label') : null, 'url' => $request->input('url'), ]; $link = (new CreateProjectLink)->execute($data); return response()->json([ 'data' => [ 'id' => $link->id, 'type' => $link->type, 'label' => $link->label, 'url' => $link->url, ], ], 201); } // ...}
We can also look at how this service is tested.
tests/Unit/Services/Company/Project/CreateProjectLinkTest.php:
use Illuminate\Validation\ValidationException;use App\Services\Company\Project\CreateProjectLink; class CreateProjectLinkTest extends TestCase{ // ... /** @test */ public function it_fails_if_wrong_parameters_are_given(): void { $request = [ 'first_name' => 'Dwight', ]; $this->expectException(ValidationException::class); (new CreateProjectLink)->execute($request); } // ...}
There are more tests. You can check them in the GitHub repository.
In this example, we have codenteq/laerx open-source project and an InvoiceService
class with two methods for creating and deleting invoices.
app/Services/Admin/InvoiceService.php:
use App\Http\Requests\Admin\CompanyRequest;use App\Models\Invoice;use App\Models\Package;use App\Models\PaymentPlan;use Carbon\Carbon; class InvoiceService{ private $price = null; private $month = null; private $packageId = null; public function __construct() { $request = request(); if ($request->planId) { $plan = PaymentPlan::find($request->planId); $package = Package::where('planId', $request->planId)->first(); $this->price = $package->price; $this->packageId = $package->id; $this->month = $plan->month; } } public function store(CompanyRequest $request, $id): void { Invoice::create([ 'price' => $this->price, 'start_date' => $request->start_date, 'end_date' => Carbon::create($request->start_date)->addMonths($this->month), 'packageId' => $this->packageId, 'companyId' => $id, ]); } public function destroy($id): void { Invoice::where('companyId', $id)->delete(); }}
The Service usage can be found in another Service, CompanyInfoService
.
app/Services/Admin/CompanyInfoService.php:
use App\Http\Requests\Admin\CompanyRequest;use App\Jobs\ImageConvertJob;use App\Models\CompanyInfo;use App\Services\ImageConvertService; class CompanyInfoService{ private $invoiceService; protected $convertService; public function __construct(InvoiceService $invoiceService, ImageConvertService $convertService) { $this->invoiceService = $invoiceService; $this->convertService = $convertService; } public function store(CompanyRequest $request, $id): void { // ... $this->invoiceService->store($request, $id); } // ... public function destroy($id): void { CompanyInfo::where('companyId', $id)->delete(); $this->invoiceService->destroy($id); }}
In this example, we have a realodix/urlhub open-source project and a VisitorService
service, which has a create()
method and two methods for other logic.
use App\Helpers\Helper;use App\Models\Url;use App\Models\User;use App\Models\Visit;use Spatie\Url\Url as SpatieUrl; class VisitorService{ public function __construct(public User $user) {} public function create(Url $url) { $logBotVisit = config('urlhub.track_bot_visits'); $device = Helper::deviceDetector(); $referer = request()->header('referer'); if ($logBotVisit === false && $device->isBot() === true) { return; } Visit::create([ 'url_id' => $url->id, 'visitor_id' => $this->user->signature(), 'is_first_click' => $this->isFirstClick($url), 'referer' => $this->getRefererHost($referer), ]); } public function isFirstClick(Url $url): bool { $hasVisited = $url->visits() ->whereVisitorId($this->user->signature()) ->exists(); return $hasVisited ? false : true; } public function getRefererHost(?string $value): ?string { if ($value === null) { return null; } $referer = SpatieUrl::fromString($value); return $referer->getScheme() . '://' . $referer->getHost(); }}
This service can be found in the UrlRedirectController
controller.
app/Http/Controllers/UrlRedirectController.php:
use App\Models\Url;use App\Services\UrlRedirection;use App\Services\VisitorService;use Illuminate\Support\Facades\DB; class UrlRedirectController extends Controller{ public function __invoke(Url $url) { return DB::transaction(function () use ($url) { app(VisitorService::class)->create($url); return app(UrlRedirection::class)->execute($url); }); }}
We can also check the Unit test for this service.
tests/Unit/Services/VisitorServiceTest.php:
use App\Services\VisitorService;use PHPUnit\Framework\Attributes as PHPUnit;use Tests\TestCase; #[PHPUnit\Group('services')]class VisitorServiceTest extends TestCase{ private VisitorService $visitorService; protected function setUp(): void { parent::setUp(); $this->visitorService = app(VisitorService::class); } #[PHPUnit\Test] #[PHPUnit\Group('u-service')] public function getRefererHost(): void { $this->assertSame(null, $this->visitorService->getRefererHost(null)); $this->assertSame( 'https://github.com', $this->visitorService->getRefererHost('https://github.com/laravel'), ); $this->assertSame( 'http://urlhub.test', $this->visitorService->getRefererHost('http://urlhub.test/admin?page=2'), ); }}
In this example, we have amitavroy/doctor-app open-source project and an AppointmentService
service with a getAppointments()
method.
app/Services/AppointmentService.php:
use App\Models\Appointment; class AppointmentService{ public function getAppointments($today = false, $confirmed = true) { return Appointment::query() ->with(['patient' => function ($query) { $query->select([ 'id', 'name', 'weight', 'phone_number', 'patient_id', ]); }], ['location' => function ($query) { $query->select([ 'location.name', ]); }]) ->when($today, function ($query) { $query->where('date', now()->format('Y-m-d')); }) ->when($confirmed === true, function ($query) { $query->where('visited', 1)->where('time', '!=', null); }) ->when($confirmed === false, function ($query) { $query->where('visited', 0); }) ->orderByDesc('date') ->orderByDesc('id') ->paginate(20); }}
This service method is used in the HomeController
and AppointmentController
Controllers.
app/Http/Controllers/HomeController.php:
use App\Services\AppointmentService; class HomeController extends Controller{ public function __invoke(AppointmentService $appointmentService) { $appointments = $appointmentService->getAppointments(true, false); return Inertia::render('Home') ->with('appointments', $appointments); }}
app/Http/Controllers/AppointmentController.php:
use App\Services\AppointmentService; class AppointmentController extends Controller{ private $appointmentService; public function __construct(AppointmentService $appointmentService) { $this->appointmentService = $appointmentService; } public function index() { $appointments = $this->appointmentService->getAppointments(false, false); return Inertia::render('Appointments') ->with('appointments', $appointments); } // ...}
In this example, we have idanieldrew/redact open-source project and an AuthService
service with six methods.
Modules/Auth/Services/v2/AuthService.php:
// ... use Module\Auth\Services\AuthService as Service; class AuthService extends Service{ public function store($request): array { $user = $this->model()->create([ 'username' => $request->name, 'email' => $request->email, 'phone' => $request->phone, 'password' => Hash::make($request->password), ]); $token = $user->createToken('token')->plainTextToken; return [ 'status' => 'success', 'code' => Response::HTTP_CREATED, 'message' => 'Successfully registered', 'data' => [ 'user' => $user, 'token' => $token, ], ]; } public function login($request): array { $user = User::whereEmail($request->email)->first(); // Check exist user if (!$user || !Hash::check($request->password, $user->password)) { return $this->response( 'error', Response::HTTP_UNAUTHORIZED, 'invalid email or password', null ); } $token = $user->createToken('test')->plainTextToken; return $this->response( 'success', Response::HTTP_OK, 'Successfully login', ['user' => $user, 'token' => $token] ); } public function forgetPassword(string $field) { $data = filter_var($field, FILTER_VALIDATE_EMAIL) ? 'email' : 'phone'; // check exist user $user = (new UserRepository)->getCustomRow($data, $field); if (!$user) { return $this->response('fail', Response::HTTP_UNAUTHORIZED, 'email not found', null); } $token = Str::random(5); $request = new stdClass(); $request->token = $token; $request->data = $data; $request->field = $field; $request->type = "$data verified"; (new TokenRepository())->store($user, $request); (new ForgetPassword)->forgetPassword(new ForgetPasswordEmail($user, $token)); return $this->response('success', Response::HTTP_OK, 'send token for forgot password', null); } public function changePsd($request) { if (!Hash::check($request->old_password, auth()->user()->password)) { return $this->response('fail', 400, "mot match", null); } $user = auth()->user(); $user = tap($user, function ($user) use ($request) { $user->update([ 'password' => Hash::make($request->password) ]); }); return $this->response('success', 200, "match", new UserResource($user)); } public function verfyToken(string $token) { $res = (new TokenRepository)->existToken($token); return $res ? $this->response('success', '200', 'correct', null) : $this->response('fail', '404', 'incorrect', null); } public function verifyHandler(User $user, array $data) { return (new StatusRepository)->update($user, $data); } private function response(string $status, int $code, string $message, $data): array { return [ 'status' => $status, 'code' => $code, 'message' => $message, 'data' => $data ?: null, ]; }}
Each service in this project extends some base service where the Model is set.
Modules/Auth/Services/AuthService.php:
use Module\Share\Service\Service;use Module\User\Models\User; class AuthService implements Service{ public function model(): \Illuminate\Database\Eloquent\Builder { return User::query(); }}
This AuthService
is called in multiple places. One of which, for example, is AuthController
.
Modules/Auth/Http/Controllers/v2/AuthController.php:
// ... use Module\Auth\Services\v2\AuthService;use Module\Share\Contracts\Response\ResponseGenerator; class AuthController extends Controller implements ResponseGenerator{ private AuthService $service; public function __construct() { $this->service = resolve(AuthService::class); } public function register(RegisterRequest $request) { $store = $this->service->store($request); return $this->res($store['status'], $store['code'], $store['message'], $store['data']); } public function login(LoginRequest $request): \Illuminate\Http\JsonResponse { $login = $this->service->login($request); return $this->res($login['status'], $login['code'], $login['message'], $login['data']); } public function res(string $status, int $code, string|null $message, array|int|ResourceCollection|JsonResource $data = null): JsonResponse { return response()->json([ 'status' => $status, 'message' => $message, 'data' => ! $data ? null : [ 'user' => new UserResource($data['user']), 'token' => $data['token'], ], ], $code); }}
execute()
: Similar to Queueable JobIn this example, we have a monicahq/monica open-source project and a CancelAccount
service, which has an execute()
method.
The service also has validation rules, permissions, and one private method for the logic.
app/Domains/Settings/CancelAccount/Services/CancelAccount.php:
use App\Interfaces\ServiceInterface;use App\Models\Account;use App\Models\File;use App\Services\QueuableService; class CancelAccount extends QueuableService implements ServiceInterface{ public function rules(): array { return [ 'account_id' => 'required|uuid|exists:accounts,id', 'author_id' => 'required|uuid|exists:users,id', ]; } public function permissions(): array { return [ 'author_must_belong_to_account', 'author_must_be_account_administrator', ]; } public function execute(array $data): void { $this->validateRules($data); $account = Account::findOrFail($data['account_id']); $this->destroyAllFiles($account); $account->delete(); } private function destroyAllFiles(Account $account): void { $vaultIds = $account->vaults()->select('id')->get()->toArray(); File::whereIn('vault_id', $vaultIds)->chunk(100, function ($files) { $files->each(function ($file) { $file->delete(); }); }); }}
This service can be found in the CancelAccountController
controller.
app/Domains/Settings/CancelAccount/Web/Controllers/CancelAccountController.php:
// ... use App\Domains\Settings\CancelAccount\Services\CancelAccount; class CancelAccountController extends Controller{ // ... public function destroy(Request $request) { if (! Hash::check($request->input('password'), Auth::user()->password)) { throw new ModelNotFoundException('The password is not valid.'); } $data = [ 'account_id' => Auth::user()->account_id, 'author_id' => Auth::id(), ]; CancelAccount::dispatch($data); return response()->json([ 'data' => route('login'), ], 200); }}
Services in this project are treated as Jobs that run in a Queue, which is why they are dispatched. Every Service extends the QueuableService
, which is basically a Job class.
app/Services/QueuableService.php:
use Illuminate\Bus\Queueable;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Foundation\Bus\Dispatchable;use Illuminate\Queue\InteractsWithQueue;use Throwable; abstract class QueuableService extends BaseService implements ShouldQueue{ use Dispatchable, InteractsWithQueue, Queueable; public int $tries = 1; public function __construct( public ?array $data = null ) { if ($data !== null) { $this->validateRules($data); } } public function handle(): void { $this->execute($this->data ?? []); } abstract public function execute(array $data): void; public function failed(Throwable $exception): void { // }}
In this example, we have the jigar-dhulla/exchange-rate open-source project and an ExchangeRateHost
service with two methods: convert()
and getAllowedCurrencies()
.
app/Services/ExchangeRateHost.php:
// ... use App\Services\Contract\ExchangeRate as ExchangeRateContract; class ExchangeRateHost implements ExchangeRateContract{ private const API_URL = 'https://api.exchangerate.host'; public function convert(string $from, string $to): float { $cacheKey = sprintf('%s-%s-%s', __CLASS__, $from, $to); $ttl = (int) config('services.conversion.ttl', 3600); return Cache::remember($cacheKey, $ttl, function () use ($from, $to) { $uri = sprintf('/convert?from=%s&to=%s', $from, $to); $response = Http::get(self::API_URL . $uri); $array = $response->json(); if(!$array['success'] ?? false){ Log::error("Error in API Response of Exchange Rate Conversion", [ 'class' => __CLASS__, 'from' => $from, 'to' => $to, 'response' => $response->body(), 'status' => $response->status(), ]); throw new ExchangeRateException("Could not convert from $from to $to"); } return (float) $array['result']; }); } public function getAllowedCurrencies(): array { $cacheKey = sprintf('%s-%s', __CLASS__, 'symbols'); $ttl = (int) config('services.symbols.ttl', 3600); return Cache::remember($cacheKey, $ttl, function (){ $response = Http::get(self::API_URL . '/symbols'); $array = $response->json(); if(!$array['success'] ?? false){ Log::error("Error in API Response of fetching symbols", [ 'class' => __CLASS__, 'response' => $response->body(), 'status' => $response->status(), ]); throw new ExchangeRateException("Could not fetch symbols"); } return array_keys($array['symbols']); }); }}
This Service is used in the Conversion
Livewire component.
use App\Services\Contract\ExchangeRate as ExchangeRateContract; class Conversion extends Component{ // ... public function mount(ExchangeRateContract $service) { try { $symbols = $service->getAllowedCurrencies(); $this->currencies = array_filter($symbols, fn ($symbol) => in_array($symbol, self::ALLOWED_SYMBOLS)); $this->conversions = $this->getConversions(); } catch (ExchangeRateException $e) { session()->flash('api_error', $e->getMessage()); } } // ... public function convert(ExchangeRateContract $service) { $this->result = null; $this->validate(); try { $this->result = $service->convert($this->from, $this->to); } catch (ExchangeRateException $e) { session()->flash('api_error', $e->getMessage()); } if($this->result){ ConversionModel::updateOrCreate([ 'from' => $this->from, 'to' => $this->to, 'conversion_date' => now()->format('Y-m-d'), ], [ 'result' => $this->result, ]); } } // ...}
Interestingly, the Livewire component type-hints the interface of ExchangeRateContract
, not the Service class itself.
The place where it's resolved is the AppServiceProvider
.
app/Providers/AppServiceProvider.php:
use App\Services\Contract\ExchangeRate as ExchangeRateContract;use App\Services\ExchangeRateHost; class AppServiceProvider extends ServiceProvider{ // ... public function boot(): void { $this->app->bind(ExchangeRateContract::class, ExchangeRateHost::class); }}
The reason for such binding is the possibility of replacing this Service class with a Dummy Service for testing.
That class actually exists, with hard-coded static values:
use App\Services\Contract\ExchangeRate as ExchangeRateContract; class Dummy implements ExchangeRateContract{ private const ALLOWED_CURRENCIES = [ 'EUR', 'INR', ]; public function convert(string $from, string $to): float { return 90.00; } public function getAllowedCurrencies(): array { return self::ALLOWED_CURRENCIES; }}
At the time of writing this article, this Dummy Service isn't actually used in the project, but the whole structure is prepared for such replacement.
I hope that from the examples above, you get a complete picture of the practice of using it.
But, to re-cap, the primary benefits of Services are: