1
\$\begingroup\$

When refactoring and old class in PHP, I noticed that it contained a lot of business logic and thus was not allowing code reuse. So I thought about composition in order to achieve horizontal code reuse (since inheritance permits only vertical reuse) and the possibility to do Dependency Injection eventually. DI gives then the possibility to use Mock Services in order to test the classes.

Basically, that class permits sending SMS using the API of a third-party and we have hundreds of APIs we need to deal with, so this is why the idea of horizontal code reuse would be useful.

When we send an SMS, we need to:

  1. Send an HTTP request to some URL provided by the Carrier with the right body contents (phone number, SMS title, SMS content and other information, depending on the carrier) and in the right format (XML, JSON, SOAP, depending on the carrier)
  2. Log the sent SMS*in the database (phone number, title, contents, delivery_status). The table where it is logged is used for tracking all sent SMSses. Also, we attach a delivery_status code (1 for delivered, -1 for not delivered) because some carriers send us then a Delivery Report (by requesting a script on our end) if the phone acknowledges the delivery.
  3. Log the sent SMS specifically for that Carrier in another dedicated table that contains more fields than (phone_number, title, message, delivery_status). Those fields could be used in the future for special purposes.

I decided to separate the logic in mainly three classes:

  • LoggerService: Class that logs the sent sms informations in the database (also logs the carrier-specific sent sms informations)
  • RequestMaker: Trait that simply makes HTTP requests and returns the result without any further processing
  • SerializerService: Service that serializes the body of the request (converts from a dictionary of parameters to a specific format required by the carrier like XML or JSON, etc.). XMLBodySerializer is one implementation of that service. JsonBodySerializer is another.
  • SmsSender: The core class that is using the RequestMaker trait and the two other services in order to perform an API call to the specific carrier's API. If a carrier named MyCarrier exists, we would have a MyCarrierSender class inheriting SmsSender. The main task of that class would be to build the right request body and to pass it to the serializer, then to the request maker which will call the API. Finally, the class should fill the sent SMS DTO that will the be logged using the LoggerService currently in use.

Do you think it is a good idea to adopt this design? In other words, does it meet the goals of:

  • Decoupling business logic
  • Code reusing
  • Permitting testability

Here is an implementation of the architecture:

interface SmsSender {
    public function initServices(
        ILoggerService $resultLoggerService, // That Logs information about sent SMS in the databse
        IBodySerializer $serializerService, // Request Body Serializer
        );
    public function sendText(
        $msisdn,
        $title="",
        $text="",
        $options=array());
}

trait RequestMaker {
    public function makeGet($url, $params=array());
    public function makePost($url, $params=array(), $body);
    public function buildUrl($url, $params=array());
    public function request($url, $method=null, $params=array(), $body=array());
}

interface IResultLoggerService {
    // Logs the SMS sent to the database
    public function logResult($sms2Dto);
    // Logs sent SMS specific informations for that Carrier
    public function logInMt($specificMtDto);

}

/**
* Request Body Serializer (XMLBodySerializer, JSONBodySerializer implements it) that serializes the body of the request
*/
interface IBodySerializer {
    public function serialize($data);
    public function parse($response);
}

/**
 *  Example of an SMS Sender for a specific Carrier
*/

class MyCarrierSmsSender implements SmsSender {
   use RequestMaker;

   public function __construct() {
        $this->initServices(
            new MyCarrierLoggerService(),
            new XMLBodySerializer() // Will use XML for communication with the carrier's API
        );
    }

    protected function preSend($msisdn, $title, $text, $options) {
        /**
        * Performs some verifications, prepares some variables
        * ....
        */
        // Example :
        $sentSms = new Sms2Dto();
        $sentSms->setMsisdn($msisdn);
        // etc.
        $this->setSms2(); // Set the Sent SMS DTO
    }
    public function sendText($msisdn, $title, $body, $options = array()) {
        $this->preSend($msisdn, $title, $body, $options);
        $data = {
             // .... prepare request data
        }

        // Serialize data using the injected service
        $raw_data = $this->getSerializerService()->serialize($data);

        // Call request() from the RequestMaker trait
        $raw_result = $this->request($url, array(), $raw_data, "POST");

        // Deserialize API's response 
        $result= $this->getSerializerService()->parse($raw_result);

        // Interprets API's response and returns a SUCCESS or FAIL status code
        $status = $this->interpretResult($result);

        $this->postSend($status);

        return $status;
    }
    protected function postSend($status) {
        if($status == SUCCESS) {
              // Log sent SMS
              $this->getLoggerService()->logResult($this->getSms2Dto());
              // Log specific sent SMS by this carrier (contains more useful and specific info to that carrier)
              $this->getLoggerService()->logInMt($this->getMtDto());
         }
    }
}

There are also DTOs: Sms2Dto and {Carrier}MtDto for every Carrier. Before that design, everything here was done in one class. There was a class for each carrier so a lot of code was repeated.

\$\endgroup\$
  • \$\begingroup\$ Here is a brief snippet of code to explain it better the description should explain the code because the code is being reviewed here, not the other way around :-) \$\endgroup\$ – t3chb0t Dec 29 '16 at 20:24
  • \$\begingroup\$ You are right. I added an explanation :) Thanks \$\endgroup\$ – Adam cherti Dec 29 '16 at 20:50
  • \$\begingroup\$ What I think : Can you know UML ? You can design classes dependencies and sequences diagram to display all exchanges between methods from classes. What I see : You can extract '$url' and '$params' from your trait 'RequestMaker' and make get/setters. You can optimize 'preSend' and 'sendText' by the same way. If your carrier is specific to a business logic, you don't have to manage dependency injection (from your contructor). \$\endgroup\$ – Weenesta - Mathieu Dormeval Jan 1 '17 at 9:03
  • \$\begingroup\$ For now I can suggest a few tips to get a better approach of a good result/design. Bridge design pattern for the document creation (XML, Json. Observer or Mediator pattern for log, it have to be based on how or when you need to log. PSR-3 for the log interface. \$\endgroup\$ – Gabriel Heming Jan 2 '17 at 11:21
  • 1
    \$\begingroup\$ the pattern Bridge can be applied for everything that you need create a new document and for your case, yes. You'll have the same "constructor" and apply your "skeleton" document . PSR-3 defines an interface for universal log, not only for framework or CMS, it's just a log, nothing less, nothing more. If you have to use a log, considere to use PSR-3. A log can throw an error, can show a information, can store something or consume a web service. Your implementation defines it, not the interface. You can change between log implementations (SMS, XML, etc) but the interface remains the same. \$\endgroup\$ – Gabriel Heming Jan 4 '17 at 10:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.