我目前正在開發一個私人休息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原則。
如果創建新產品並完成初始/產品錯誤檢查,則產品會在產品數據庫表中獲取一個新行。但是,如果在完成此操作並進入子資源創建並且由於在初始請求中傳遞的數據而導致任何子資源創建失敗,則初始請求只會部分成功,因爲來自這些子資源的錯誤將阻止創建具體錯誤的子資源並與最初創建的產品關聯。
所以這裏是我的一些想法..
我想可能實現一個完全不理會插入/更新,並通過所有父/子端點錯誤處理,看是否運行數據的預演方法數據很乾淨。我並不完全知道如何將它合併到端點流程中,而不會過度複雜化和破壞代碼流的可讀性。
任何其他想法或改變執行流程,將解決這個問題真的會被讚賞,讓我指出最佳方法的正確方向。
謝謝!
我最終使用PHP MySQLi類事務方法,並在執行期間的任何階段的事務期間出現錯誤時支持回滾,同時仍支持腳本執行中的ID依賴關係。我相信用一塊石頭解決了兩隻鳥。 –
軟件的終極測試是「它工作」。聽起來像你找出了一個工作場景。 –