Looking at litestar-pg-redis-docker and litestar-fullstack examples, I try to figure out what's the purpose of Service? Seems like Service delegates everything to the underlying Repository, not adding anything?
In my app I'll need to have a batch script with mix of python / custom sqlalchemy queries logic that will be run from cli or as a query job. Is Service a good place for this script?
#What's the purpose of lib/service.py in the examples?
1 messages · Page 1 of 1 (latest)
yhea we need to refactor this away
so basically we had the logic in service and than we introduced the repositories into the core
so this is now pretty redundant
the entire example is slated for a refactor
@lapis ravine - are you looking into this?
if not, we are open to PRs basically
Service extends Repository . In this architecture
- Repository is to subclass and add new functionaility to reposiotory.
- Where services are design to override
For example if you want something to do before insert , after insert / before update/ after update . Service is a good place. .
It would look a bit complicated at first but very useful.
Here is my plugin architecutre in my spritlog
class Service(SQLAlchemyAsyncRepositoryService[Backlog]):
repository_type = Repository
plugins: set[BacklogPlugin] = set()
def __init__(self, **repo_kwargs: Any) -> None:
self.repository: Repository = self.repository_type(**repo_kwargs)
self.model_type = self.repository.model_type
super().__init__(**repo_kwargs)
async def to_model(self, data: Backlog | dict[str, Any], operation: str | None = None) -> Backlog:
if isinstance(data, Backlog):
slug = await self.repository.get_available_backlog_slug(backlog=data)
if isinstance(slug, str):
data.slug = slug
data.due_date = await self.repository._get_due_date(data.beg_date, data.est_days)
return await super().to_model(data, operation)
async def create(self, data: Backlog | dict[str, Any]) -> Backlog:
# Call the before_create hook for each registered plugin
for plugin in self.plugins:
data = await plugin.before_create(data=data)
obj = await super().create(data)
# Call the after_create hook for each
for plugin in self.plugins:
after = await plugin.after_create(data=obj)
obj = await super().update(obj.id, after)
return obj
async def update(self, item_id: Any, data: Backlog | dict[str, Any]) -> Backlog:
# Call the before_update hook for each registered plugin
for plugin in self.plugins:
data = await plugin.before_update(item_id=item_id, data=data)
obj: Backlog = await super().update(item_id, data)
# Call the after_update hook for each registered plugin
for plugin in self.plugins:
await plugin.after_update(data=obj)
return obj
here i overwritten to_model which is called before update or insert
it creates slug , and calculates due_date
then those services are injected into dependancies like this
logger = log.get_logger()
if TYPE_CHECKING:
from collections.abc import AsyncGenerator
from sqlalchemy.ext.asyncio import AsyncSession
async def provides_service(db_session: AsyncSession) -> AsyncGenerator[Service, None]:
plugins = []
for _, name, _ in pkgutil.iter_modules([app.plugins.__path__[0]]):
module = __import__(f"{app.plugins.__name__}.{name}", fromlist=["*"])
for obj_name in dir(module):
obj = getattr(module, obj_name)
if isinstance(obj, type) and issubclass(obj, BacklogPlugin) and obj is not BacklogPlugin:
plugins.append(obj())
async with Service.new(
session=db_session,
statement=select(Backlog).order_by(Backlog.due_date).options(joinedload(Backlog.project)),
) as service:
service.plugins = set(plugins)
try:
yield service
finally:
...
that way we can use services in controllers , instead of repositories. : making CRUD easily.
@get()
async def filter(self, service: "Service", filters: list["FilterTypes"] = validation_skip) -> Sequence[Model]:
return await service.list(*filters)
@post()
async def create(self, data: Model, current_user: User, service: "Service") -> Model:
if not data.owner_id:
data.owner_id = current_user.id
if not data.assignee_id:
data.assignee_id = current_user.id
return await service.create(data)
fullstack? or pg-redis-docker ? i think pg-redis is fine as it is. it is middle ground between fullstck and helloworld
oh but its lib need to be refactored and cleaned up
i have in mind to build django-like generators ,
also copier based starter templates.
Thanks for the real world example, really useful.
Now let's say we want to create a batch script for cron that would update Backlog status according some logic, requiring custom SqlAlchemy queries, may records at once. What would be a good place to add such code? Maybe as a method of Service?
Yep. With the logic in the service, you can then call that anywhere you need.
That's the main benefit of this pattern, imo
is that it helps absstract the logic from the controller so that you can re-use it in other places if you need to
I would use python instead of bash anyday. Python is a lot better than bash. even if it is a single line of code.
- python is both faster and readable than bash , and much easier to get things done.
The way I think of it is that the service layer is where the business logic lives. This means that the boilerplate around using the framework and database remains fairly static, and mostly doesn't change from one project to the next.