REST API - 共同依赖的端点干运行流程?

时间:2016-06-10 18:26:24

标签: php api rest http design-patterns

我目前正在开发一个私有的api api,并且在发布以创建父级资源时遇到了子资源的依赖性问题。

例如......

我有以下父端点..

/products

以下子端点相对于/ products ..

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

使用此休息api,/ products还可以接受以下密钥的后期有效负载:'类别','媒体'' shippingZones', '变体&#39 ;.

如果设置了这些密钥中的任何一个,/ products端点将向下钻取并根据有效负载密钥创建和关联子资源,分别用于/ products的当前发布请求。

这是在对/ products的POST请求中执行的代码,显示了当前如何处理此代码。在你花点时间浏览下面之后,我会解决手头的问题,也许在我解释之前你会看到这个问题?

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。

这会产生一个相互依赖的问题并维持所有或没有原则。

如果创建了新产品并且初始/产品错误检查已完成,那么产品将在产品数据库表中获得一个新行。但是,如果在此之后完成并继续创建子资源并且由于初始请求中传递的数据导致任何子资源创建失败,初始请求仅部分成功,因为来自这些子资源的错误将阻止创建特定错误的子资源并与最初创建的产品相关联。

所以这里有一些我的想法......

我希望可能实现一种完全忽略插入/更新的干运行方法,并通过所有父/子端点错误处理来运行数据,以查看数据是否干净。我不完全确定如何将其合并到端点流中,而不会过度复杂化并破坏代码流的可读性。

任何其他想法或对执行流程的更改都可以解决这个问题,我真的很感激能够指出最佳方法的正确方向。

谢谢!

1 个答案:

答案 0 :(得分:1)

您遇到的问题是交易处理。最终必须处理事务和回滚。是的,这可能是你的PHP程序的一个难点,因为你必须得到ID,但如果这些是真正的交易,你必须在没有先插入产品时失败。

如果一个选项使您更容易,那么将一个选项推送到持久层(数据库?)。存储过程在这里是一个选项,因为该过程可以设置为通过或失败,然后发回适当的代码以指示发生了什么(以及可能的错误信息,如果需要)。关系数据库通常有相当简单的方法来建立这种类型的处理,而NoSQL已被击中或遗漏。

在这种情况下,PHP变得更简单,但您开始维护一些数据库代码。

您可以设置空运行,拉出任何约束并运行数据。这样做可能对你有一定的价值。但它无法解决系统正常运行的问题。你仍将面临的问题。方法中是否有价值取决于您将从练习中获得哪些信息。如果做得恰当,你就会创建一个准单元测试,它应该产生一些价值。