2016-06-10 47 views
1

我目前正在開發一個私人休息API,並在發佈時創建父級資源時遇到子資源的代碼依賴性問題。REST API - 互相依賴的端點空運行流程?

例如..

我有以下父終點..

/products 

而且相對於/產品以下兒童終點..

/products/[PRODUCT_ID]/categories 
/products/[PRODUCT_ID]/media 
/products/[PRODUCT_ID]/shippingZones 
/products/[PRODUCT_ID]/variants 

有了這個REST API, /產品還能夠接受以下按鍵的後期有效負載:「類別」,「媒體」,「運輸區域」,「變體」。

如果設置了這些密鑰中的任何一個,則/ products端點將向下鑽取,並且還分別基於/ products上的當前發佈請求創建並關聯基於有效負載密鑰的子資源。

下面是在POST請求/ products上執行的代碼,顯示了當前如何處理這些代碼。在你花點時間瀏覽下面的內容之後,我會着手解決這個問題,也許在我解釋它之前你會看到這個問題?

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 as $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'] as $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 as $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'] as $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 as $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'] as $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 as $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'] as $error) { 
        $this->addResponseError($error['parameter'], $error['message'], array('productVariants', $index, $error['parameter'])); 
       } 

       $product['productVariants'][$index] = $productVariant['data']; 
      } 
     } 

     return $product; 
    } 

好吧!現在解決問題。使用此流程,新創建產品上的/ products的子資源創建將依賴於插入到產品數據庫表中的新行,並在迭代和創建子資源之前返回產品ID,因爲如果子資源將引發錯誤未在他們的端點URI中傳遞productId。

這會產生一個codependency問題,並且維護ALL或NOTHING原則。

如果創建新產品並完成初始/產品錯誤檢查,則產品會在產品數據庫表中獲取一個新行。但是,如果在完成此操作並進入子資源創建並且由於在初始請求中傳遞的數據而導致任何子資源創建失敗,則初始請求只會部分成功,因爲來自這些子資源的錯誤將阻止創建具體錯誤的子資源並與最初創建的產品關聯。

所以這裏是我的一些想法..

我想可能實現一個完全不理會插入/更新,並通過所有父/子端點錯誤處理,看是否運行數據的預演方法數據很乾淨。我並不完全知道如何將它合併到端點流程中,而不會過度複雜化和破壞代碼流的可讀性。

任何其他想法或改變執行流程,將解決這個問題真的會被讚賞,讓我指出最佳方法的正確方向。

謝謝!

回答

1

您遇到的問題是事務處理。最終必須處理事務和回滾。是的,這可能會讓PHP程序感到痛苦,因爲您必須返回ID,但如果這些是真正的事務處理,那麼當產品未插入時必須失敗。

一個選項,如果它讓你更容易,就是將事務處理推送到持久層(數據庫?)。存儲過程是這裏的一個選項,因爲過程可以設置爲通過或失敗,然後發回適當的代碼以指示發生了什麼(如果需要,可能還包括錯誤信息)。關係數據庫通常有相當簡單的方法來實現這種類型的處理,而NoSQL已經被擊中或未命中。

在這種情況下,PHP變得更簡單,但您開始維護一些數據庫代碼。

您可以設置空運行,取消任何約束並運行數據。這樣做對你來說可能有一些價值。但它不會解決你仍然會面臨的「系統工作正常」問題。方法中是否有價值取決於你將從練習中獲得哪些信息。正確完成你創建一個準單元測試,這應該產生一些價值。

+0

我最終使用PHP MySQLi類事務方法,並在執行期間的任何階段的事務期間出現錯誤時支持回滾,同時仍支持腳本執行中的ID依賴關係。我相信用一塊石頭解決了兩隻鳥。 –

+0

軟件的終極測試是「它工作」。聽起來像你找出了一個工作場景。 –