mercredi 16 mai 2018

Create correct object relation

I'm starting to learn OOP and I know it's hard to build good, quality, testable code and I'm afraid to make some architectural mistake and the beginning because it's harder to refactor later.

Currently, I'm working on Laravel and I need a component (a small part of a program) to calculate online advertising statistics (CPM, EPC and so on) uisng cronjob. For this purpose, I need to collect data from the database, calculate statistic(s) and store it to related table. This need to be run through CLI using cronjob. Calculation of stats should be done if possible by SQL, but it not always can be done with current architecture. So I need to create some reusable component which can be easily extended with new stat calculation logic, either with just fetch already calculated logic from DB and store it or fetch, calculate and store to DB. And to have ability for futuhre to use it easily in any part of application not just by CLI.

To run it from CLI I'm using Laravel command with scheduling:

class StatsComamnd extends Command
{
    protected $signature = 'project:calculatestats {statName}';
    public function __construct(StatsService $statsService){
        parent::__construct();
        $this->statsService = $statsService;
    }
    public function handle() {
        $method = $this->argument('statName');
        if(!method_exists($this, $method)) {
            $this->error('Invalid stat name provided!');
       }

       $this->{$method}();
    }
    public function networkOffers():void {
         $this->stastService->setStatsHandler(app(OffersNetworkStatsHandler::class))->handle();
    }
    public function networkOffersCpm():void{
         app(OffersNetworkCpmHandler::class)->handle(); 
    }
    public function networkOffersEpc:void{
         app(OffersNetworkEpcHandler::class)->handle(); 
    }
    public function networkSurveys():void{
         app(SurveysNetworkHandler::class)->handle(); 
    }
    public function networkSurveysCpm():void{    
         app(SurveysNetrworkCpmHandler::class)->handle(); 
    }
    public function networkSurveysEpc:void{
         app(SurveysNetworkEpcHandler::class)->handle(); 
    }
    //...other handlers, like countryOffersCpm, toolSpecificOffersCpm and so on
}

SurveysNetrworkCpmStatsHandler:

/** This handle responsible of collectiong, calculating and storing network wide survey CPMs. We can't calculate CPM inside DB, so here we are going to use CpmCalculator */
class SurveysNetrworkCpmStatsHandler implements StatsHandlerInterface {
    private $surveyImpressionsRepo;
    private $statsRepo;
    private $vcPointRepo;
    private $calculator;
    public function __construct(
        SurveyImpressionRepositoryInterface $surveyImpressionRepository,
        SurveyStatsRepositoryInterface $statsRepository,
        VcPointRepositoryInterface $vcPointRepository,
        CpmCalculator $calculator
    ){
        $this->surveyImpressionsRepo = $surveyImpressionRepository;
        $this->calculator = $calculator;
        $this->vcPointRepo = $vcPointRepository;
        $this->statsRepo = $statsRepository;
    }
    public function handle() {
        $stats = [];
        list($impressions, $conversions) = $this->fetchStatisticData();
        foreach ($impressions as $impression) {
            $sid = $impression->survey_id;
            $conversion = $conversions->first(function($conversion) use ($sid) {
                return $conversion->survey_id === $sid;
            });
            if(!isset($conversion)) {
                continue;
            }
            $stat = new \SurveyNetworkCpmStat();
            $stat->offer_id = $impression->offer_id;
            $stat->survey_id = $sid;
            $stat->mobile_cpm = $this->calculator->setConversionCount($conversion->conversions_count_mobile)->setImpressionsCount($impression->unique_impressions_count_mobile)->setPayoutSum($conversion->payout_sum_mobile)->calculate();
            $stat->desktop_cpm  = $this->calculator->setConversionCount($conversion->conversions_count_desktop)->setImpressionsCount($impression->unique_impressions_count_desktop)->setPayoutSum($conversion->payout_sum_desktop)->calculate();
            $stat[] = $stat->toArray();
        }
        $this->store($stats)
   }

   private function fetchStatisticData(){
       $impressions = $this->surveyImpressionsRepo->getImpressionsForNetworkCpm();
       $conversions = $this->vcPointRepo->getConversionsForSurveyNetworkCpm();

       return [$impressions, $conversions];
   }

   private function store($stst): bool{
       $this->statsRepo->insert()
   }
}

SurveysNetrworkStatsHandler:

/** This handle responsible of collectiong, calculating and storing all network wide survey stats.*/
class SurveysNetrworkStatsHandler implements StatsHandlerInterface {
    private $cpmHandler;
    private $epcHandler;
    private $statsRepo;
    public function __construct(
        SurveysNetrworkCpmStatsHandler $cpmHandler,
        SurveysNetrworkEpcStatsHandler $epcHandler,
        SurveyStatsRepositoryInterface $statsRepository
    ){
        $this->cpmHandler = $cpmHandler;
        $this->epcHandler = $epcHandler;
        $this->statsRepo = $statsRepository;
    }
    public function handle() {
        $this->cpmHandler->handle();
        $this->epcHandler->handle();
    }
}

OffersNetrworkCpmStatsHandler:

etrworkCpmStatsHandler:

/** This handle responsible of collectiong, calculating and storing network wide offers CPMs. We can calculate CPM inside DB, so here do not need any Calculator */
class OffersNetrworkCpmStatsHandler implements StatsHandlerInterface {
    private $surveyImpressionsRepo;
    private $statsRepo;
    public function __construct(
        SurveyImpressionRepositoryInterface $surveyImpressionRepository,
        SurveyStatsRepositoryInterface $statsRepository
    ){
        $this->surveyImpressionsRepo = $surveyImpressionRepository;
        $this->statsRepo = $statsRepository;
    }
    public function handle() {
        $stats = [];
        $stats =  $this->fetchStatisticData();
        $this->store($stats)
   }

   private function fetchStatisticData(){
       return $this->surveyImpressionsRepo->getCpm();
   }

   private function store($stst): bool{
       $this->statsRepo->insert()
   }
}

CpmCalculator:

/** Class NetworkCpmCalculator. This object responsible for calculation of CPM. Formula to calculate cpm is payout_sum/(impressions_count/1000) */
class NetworkCpmCalculator implements StatsCalculatorInterface {
    private $payoutSum = 0;
    private $impressionsCount = 0;
    private $conversionsCount = 0;
    public function setPayoutSum(float $payoutSum = null):self{
        $this->payoutSum = $payoutSum;
        return $this;
    }
    public function setImpressionsCount(int $impressionsCount = null):self{
        $this->impressionsCount = $impressionsCount;
        return $this;
    }
    public function setConversionCount(int $conversionsCount = null):self{
        $this->conversionsCount = $conversionsCount;
        return $this;
    }
    public function calculate():int{
        if(!$this->validate()) {
            return null;
       }
       return $this->payoutSum/($this->impressionsCount/1000);
    }
    //validation here
}

I remove all validation logic here and interfaces to reduca amount of code.

Can anyone suggest any improvements, maybe I can use some patterns here? Thanks for any suggestions.

Aucun commentaire:

Enregistrer un commentaire