Where communities thrive

  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
Repo info
    hi there, i use django extensively
    is bookshelf kinda like how to use dry-py in a django project?
    Artem Malyshev
    Yes, it purpose is to be an example to unswer all real project questions.
    It isn't 100% finished, unfortunately...
    Greg Brown

    Hello, this project and all the related libraries in dry-python are simply awesome, so thank you so much!

    As an aside: I only just stumbled across dry-python by a comment on SO on implementing DDD in Django. I've been struggling for many months trying to understand DDD and was beginning to think it would be far to difficult to turn around the architectural mess of a major project that I am working on. However, stories, mappers, dependencies and this cookbook are a god-send! These tools are going to enable me to migrate towards a layered architecture utopia.

    I do have a couple of questions (sorry for the long post). I'd greatly appreciate any pointers.

    Question 1

    I'm trying to understand where the Story (from the stories library) fits in terms of the Service Layer. Is it right to say that a story is a service/task, i.e. that the presentation layer invokes? However, in the docs, the Story is referred to as a business transaction and sometimes a business object. A lot of pages on the web seem to suggest that a business object and domain object are the same things. So, is a story a service or is it a domain-layer thing?

    Question 2

    It says in the stories overview: "A business transaction doesn’t have any state" and it also states in DDD (the book) that "[the application layer] does not have state reflecting the business situation, but it can have state that reflects the progress of a task...".

    Let's say, for a story in my app, the user uploads a file which is processed asynchronously (by a set of tasks in Celery). The user (on a separate 'review' screen) sees a simple progress bar (0-100%) and then the final result of the workflow (success or some failure reason). Where in DDD do you store/retrieve this state? It makes sense, maybe, to store the overall state of the workflow on the domain object / Django model, e.g. Upload.is_validate, but then if you wanted to track where/how it failed, would you want that on the model too? And then for the progress state, currently my Celery tasks contain brittle logic to calculate and write the progress directly to a model.

    I feel like I'm missing a fundamental part of building workflows and was looking towards workflow management systems (WFMS) for answers. I really love the simplicity of Stories but I am still confused on the best practices for this kind of state.

    Artem Malyshev
    Hi, I'm glad you are enjoying the project. I'll come back to you tomorrow 😊 It's later in Russia
    Greg Brown
    Hi @proofit404, ok thanks :) BTW, I just found your slides which I think answers my first question that stories represent the services. Also, I understand there is probably a lot to explain and I don't want to take up too much of your time, so even just some links or pointers of where to look would be a massive help on its own!
    Artem Malyshev

    I'm back, sorry for the late response.

    1. Yes. Stories library is a complete solution for writing services or business logic scripts. The business object terminology comes from https://dry-rb.org/gems/dry-transaction/ library which was an initial inspiration for the stories library back in 2018.

    2. In cases where execution will be continued later like celely tasks or aiohttp coroutines we suggest to write two stories. One for schedule logic. One to be executed inside the tasks. I have this feature in mind to trace these two stories as a single one dry-python/stories#39 It will be implemented some day.

    I would ask you to suggest all your ideas where our documentation could be improved.

    Best regards, Artem.

    Greg Brown

    Thank you, @proofit404 , for your response. That helped clear things up for me a lot.

    I pulled a massive effort over the weekend and last night to fully implement a feature using stories, mappers, contracts/failure protocols and DI. I demoed it to my team today, and they were as blown away as I have been as many DDD ideas finally click into place!

    Here is some feedback on the documentation (some of these points, e.g. on testing, may stray outside the scope of each library/docs).

    In Stories

    • Define what 'DSL' means (domain-specific language?)
    • Best practices for testing use cases
      • Functional/BDT test the whole story? Unit test each story method? Or both?
      • If you want to unit test each story method, whether to mock or stub out ctx? Or is there an existing test stub that ensures the code under test adheres to the data contract on the story?
    • Better explanation of:
      • Story data contracts. I'm not sure if the docs explain contracts at all, I only got the hang of how they work by looking at the Bookshelf app and playing around myself. It wasn't apparent that the contract relies on Pydantic or what Pydantic does. An example showing the error message when the story assigns state to ctx that violates the contract would help make it clearer.
      • What passing kwarg to Success does? And what the difference is between that and assigning state to ctx?
    • It would be good to have a section in the docs or a few links to books and where to find background information, such as 'The Clean Architecture' figure that I've seen in one of your talk slides. After reading a few chapters of that book, Stories clicked into place for me.
    • More advanced usage, such as:
      • The ideas behind your 'caller story' you mentioned in your last comment

    In Mappers

    • The docs are a bit lighter than the other libraries.
    • More advanced use cases. For example:
      • How to define a nested mapper if the name on the model doesn't match the name on the entity?
      • Indication of how you might use Query Object pattern in the mapper? And whether or not mappers can be (or even, are possible to be,) dynamic.
    • A section on performance considerations/limitations, such as how you might use repositories/lazy loading/caching or other techniques to avoid serialising an extended graph of domain models every time you need to fetch an object. (This is something I'm looking into myself, I'm sure there is plenty of material out there on the issue.)
    • It isn't clear how Evaluated works? Does it expect the anotated property to have the same name as the entity property? What if these are different?
    • The methods onmapper, e.g. mapper.reader and mapper.reader.sequence decorators, are not explained explicitly nor what other options are available.

    In Dependencies

    • There is one example in the Celery contrib that looks like the injector can construct and run a Story out-of-the-box. This is made apparent in the next example where ProcessOrder implements __call__. Since Dependencies and Stories are designed to work together it would be good if the Celery contrib could run the Story without requiring __call__.
    • Would be good to document the pattern seen in Bookshelf where all the Injector scopes are defined in implemented.py.

    In the Bookshelf project

    • There aren't any unit tests around the use cases (going back to my earlier point about recommended test strategies).

    General points

    • Add installation instructions to readthedocs for all libraries. It is listed on https://dry-python.org/projects/ but not on the specific doc website. Currently, a user has to look on GitHub for stories and dependencies, and over to PyPI to find the appropriate library name for mappers.
    Greg Brown
    I also recognise a lot of those points fall under 'best practices' and general software engineering know-how, so might not be appropriate to clutter the docs of a library. It is great that you have the Bookshelf app to showcase a lot more of the real-time applications.
    Greg Brown
    Greg Brown
    RE the best practices regarding testing of stories, I've just seen the issue and the related issue here: dry-python/stories#253. It definitely makes a lot of sense to treat the actual steps as private methods and test the story as a whole based on the use case description. Do you use a specific BDT library, e.g. pytest-bdd or Behave/Cucumber?
    Artem Malyshev
    Thanks for such massive feedback! I'll try to address it in parts))

    I’m also eagerly awaiting the answers to Greg brown so I can learn

    Great 👍 questions @gregbrowndev