php - REST API - Co-dependent Endpoint Dry Run Flow? -


i'm developing private rest api , running issue of codependency of subresources when posting create parent level resource.

for example..

i have following parent endpoint..

/products 

and following child endpoints relative /products..

/products/[product_id]/categories /products/[product_id]/media /products/[product_id]/shippingzones /products/[product_id]/variants 

with rest api, /products has ability accept post payload following keys: 'categories', 'media', 'shippingzones', 'variants'.

if of these keys set, /products endpoint drill down , create , associate sub resources based on payload keys respectively current post request @ /products.

here's code executed on post request /products, showing how presently being handled. i'll issue @ hand after take moment glance through below, maybe you'll see issue before explain it?

protected function post()     {         if (!$this->validatepermissions()) {             return;         }          $productsm = new productsmodel();         $filesm = new filesmodel();          $userid = $this->controller->user['id'];         $productid = $this->getresourceidbyname('products');          $productcategories = $this->controller->payload['productcategories'];         $productmedia = $this->controller->payload['productmedia'];         $productshippingzones = $this->controller->payload['productshippingzones'];         $productvariants = $this->controller->payload['productvariants'];          $existingproduct = ($productid) ? $productsm->getsingle(array( 'id' => $productid, 'userid' => $userid )) : array();         $product = array_merge($existingproduct, $this->controller->getfilteredpayload(array(             'title',             'description',             'shippingtype',             'fileid',             'hasvariants',             'ishidden'         )));          $this->validateparameters(array( 'title' => $product['title'] ));          if ($productid && !$existingproduct) {             $this->addresponseerror('productid');         }          if ($product['shippingtype'] && !in_array($product['shippingtype'], array( 'free', 'flat', 'calculated' ))) {             $this->addresponseerror('shippingtype');         }          if ($product['fileid'] && !$filesm->getnumrows(array( 'id' => $product['fileid'], 'userid' => $userid ))) {             $this->addresponseerror('fileid');         }          if ($this->hasresponseerrors()) {             return;         }          $lastcreatedproduct = (!$existingproduct) ? $productsm->getsingle(array( 'userid' => $userid ), array( 'publicid' => 'desc' )) : array();          $product = $productsm->upsert(array( 'id' => $productid, 'userid' => $userid ), array_merge($product, array(             'publicid' => $lastcreatedproduct['publicid'] + 1,             'userid' => $userid,             'isactive' => 1,             'modified' => time(),             'created' => time()         )), array( 'publicid' ));          // product categories subresource         if (is_array($productcategories)) {             foreach ($productcategories $index => $productcategory) {                 $endpoint = "/products/{$product['id']}/categories/{$productcategory['id']}";                 $requestmethod = ($productcategory['isactive'] !== '0') ? endpoint::request_method_post : endpoint::request_method_delete;                  $productcategory = $this->executeendpointbypath($endpoint, $requestmethod, $productcategory);                 foreach ($productcategory['errors'] $error) {                     $this->addresponseerror($error['parameter'], $error['message'], array( 'productcategories', $index, $error['parameter'] ));                 }                  $product['productcategories'][$index] = $productcategory['data'];             }         }          // product media subresource         if (is_array($productmedia)) {             foreach ($productmedia $index => $media) {                 $endpoint = "/products/{$product['id']}/media/{$media['id']}";                 $requestmethod = ($media['isactive'] !== '0') ? endpoint::request_method_post : endpoint::request_method_delete;                  $media = $this->executeendpointbypath($endpoint, $requestmethod, $media);                 foreach ($media['errors'] $error) {                     $this->addresponseerror($error['parameter'], $error['message'], array( 'productmedia', $index, $error['parameter'] ));                 }                  $product['productmedia'][$index] = $media['data'];             }         }          // product shipping zones subresource         if (is_array($productshippingzones)) {             foreach ($productshippingzones $index => $productshippingzone) {                 $endpoint = "/products/{$product['id']}/shippingzones/{$productshippingzone['id']}";                 $requestmethod = ($productshippingzone['isactive'] !== '0') ? endpoint::request_method_post : endpoint::request_method_delete;                  $productshippingzone = $this->executeendpointbypath($endpoint, $requestmethod, $productshippingzone);                 foreach ($productshippingzone['errors'] $error) {                     $this->addresponseerror($error['parameter'], $error['message'], array( 'productshippingzones', $index, $error['parameter'] ));                                   }                  $product['productshippingzones'][$index] = $productshippingzone['data'];             }         }          // product variants subresource         if (is_array($productvariants)) {             foreach ($productvariants $index => $productvariant) {                 $endpoint = "/products/{$product['id']}/variants/{$productvariant['id']}";                 $requestmethod = ($productvariant['isactive'] !== '0') ? endpoint::request_method_post : endpoint::request_method_delete;                  $productvariant = $this->executeendpointbypath($endpoint, $requestmethod, $productvariant);                 foreach ($productvariant['errors'] $error) {                     $this->addresponseerror($error['parameter'], $error['message'], array( 'productvariants', $index, $error['parameter'] ));                 }                  $product['productvariants'][$index] = $productvariant['data'];             }         }          return $product;     } 

alright! problem. flow, subresource creation /products on newly created product becomes dependent on new row being inserted products database table , returning product id prior iterating down , creating sub resources, subresources throw errors if not passed productid in endpoint uri.

this creates issue of codependency , maintaining or nothing principles.

in event new product created , initial /products error checking completed, product gets new row in products database table.. however, if after done , goes on subresource creation , subresource creation fails due data passed in initial request, initial request partially succeeds errors sub resources prevent erroring subresource being created , associated created product.

so here's few of ideas..

i'd potentially implement dry run approach totally ignores inserts / updates , runs data through parent / child endpoint error handling see if data clean. i'm not sure how merge flow of endpoints without overly complexifying , breaking readability of code flow.

any other ideas or changes execution flow resolve appreciated me pointed in proper direction of best approach.

thanks!

the issue having transaction processing. has handle transactions , rollbacks. yes, may pain in php program, since have id back, if these transactions, must fail when product not inserted first.

one option, if makes easier you, push transaction processing persistence layer (database?). stored procedure 1 option here, procedure can set pass or fail , send proper codes indicate happened (and possibly error information, if required). relational databases have easy methods instituted type of processing, while nosql has been hit or miss.

in scenario, php becomes simpler, start maintaining database code.

you can set dry run, pull off constraints , run data through. may have value way. not solve "will system work properly" problem still face. whether there value in approach depends on information gain out of exercise. done create quasi-unit test, should yield value.


Comments

Popular posts from this blog

sequelize.js - Sequelize group by with association includes id -

android - Robolectric "INTERNET permission is required" -

java - Android raising EPERM (Operation not permitted) when attempting to send UDP packet after network connection -