|  | @@ -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:
 |