|
@@ -7,6 +7,9 @@ from typing import Optional
|
|
|
from typing import Type
|
|
|
from typing import TypeVar
|
|
|
|
|
|
+import backoff
|
|
|
+
|
|
|
+from clean_python.base.domain import Conflict
|
|
|
from clean_python.base.domain import Filter
|
|
|
from clean_python.base.domain import Id
|
|
|
from clean_python.base.domain import Json
|
|
@@ -17,7 +20,6 @@ from clean_python.base.domain import RootEntity
|
|
|
|
|
|
T = TypeVar("T", bound=RootEntity)
|
|
|
|
|
|
-
|
|
|
__all__ = ["Manage"]
|
|
|
|
|
|
|
|
@@ -42,7 +44,24 @@ class Manage(Generic[T]):
|
|
|
async def create(self, values: Json) -> T:
|
|
|
return await self.repo.add(values)
|
|
|
|
|
|
- async def update(self, id: Id, values: Json) -> T:
|
|
|
+ async def update(self, id: Id, values: Json, retry_on_conflict: bool = True) -> T:
|
|
|
+ """This update has a built-in retry function that can be switched off.
|
|
|
+
|
|
|
+ This because some gateways (SQLGateway, ApiGateway) may raise Conflict
|
|
|
+ errors in case there are concurrency issues. The backoff strategy assumes that
|
|
|
+ we can retry immediately (because the conflict is gone immediately), but it
|
|
|
+ does add some jitter between 0 and 200 ms to avoid many competing processes.
|
|
|
+
|
|
|
+ If the repo.update is not idempotent (which is atypical), retries should be
|
|
|
+ switched off.
|
|
|
+ """
|
|
|
+ if retry_on_conflict:
|
|
|
+ return await self._update_with_retries(id, values)
|
|
|
+ else:
|
|
|
+ return await self.repo.update(id, values)
|
|
|
+
|
|
|
+ @backoff.on_exception(backoff.constant, Conflict, max_tries=10, interval=0.2)
|
|
|
+ async def _update_with_retries(self, id: Id, values: Json) -> T:
|
|
|
return await self.repo.update(id, values)
|
|
|
|
|
|
async def destroy(self, id: Id) -> bool:
|