Separation of Concerns
Perhaps the single most important thing I've learned in my development career is the concept of separation of concerns. At a high level all this means is different pieces of your codebase have one concern and one concern only. However, it's a bit trickier than it sounds. It's often very easy to just keep tossing more and more code together in order to get a feature done or complete a task. You'll soon realize though that once you begin to draw distinct boundaries between areas of responsibility in your code it quickly becomes much more modular (and testable!).
To make the concept a bit more concrete let me outline how I've typically structured my code. There are a couple of main concepts organized in to data access objects (DAOs), business logic objects (BL), models, and controllers.
DAOs are essentially what they sound like. This is where your data access happens. It's meant to be sort of dumb in that it's purely data access. Very little actual logic should happen here. An example (using Doctrine and PHP) of what this might look like is below.
<?php
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
class AlertRepository extends ServiceEntityRepository
{
public function findAllNotAcknowledgedForUser(string $userId): array
{
return $this->findBy([
'sentTo' => $userId,
'acknowledgedAt' => null,
], [
'createdAt' => 'ASC',
]);
}
}
DAOs work very closely with models in that DAOs essentially create and populate models with data. However, keeping the line between models and DAOs is important. This means you can change where data is loaded from in your DAO but it doesn't require you to change the model itself. Decided your Web Scale™ database isn't cutting it? Switch to MySQL by just updating your DAOs!
The main glue of all of this becomes your business logic classes. These classes may have numerous DAOs injected in to them as well as various other helper classes (HTTP clients for API integrations, helper utilities, etc). This is the main logic that ties these various services together to accomplish the main functionality of what you are building.
For instance, below see an example of what a business logic class might look like that helps encapsulate some logic around the Slack API.
<?php
namespace App\BusinessLogic;
use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
use App\DataAccess\ChannelDao;
class SlackBusinessLogic
{
private
$slackApi,
$channelDao;
public function __construct(
Client $slackApi,
ChannelDao $channelDao
) {
$this->slackApi = $slackApi;
$this->channelDao = $channelDao;
}
public function importGroups()
{
// See https://api.slack.com/methods/groups.list
do {
$requestData = [
'token' => $this->getAdminAccessToken(),
'limit' => 100,
'exclude_members' => false,
];
$response = $this->slackApi->get('groups.list', [
RequestOptions::QUERY => $requestData,
]);
... more logic here ...
}
}
}
In this set up controllers then become very slim. They essentially take in a request then find the relevant pieces of it to pass along to some business logic classes. Then they take the returned result and send a response back.
That's essentially all there is to it. The main point is that it will become much easier to work with your codebase if various chunks of logic are properly compartmentalized.
For other tidbits I've picked up along the way, checkout my previous post about development tips I wish I knew earlier.