Check the Kotlin version readme.
brew install pyenvpyenv install 3.10.3 && pyenv local 3.10.3curl -sSL https://install.python-poetry.org | python3 -poetry installpoetry shell
- Run app:
uvicorn web.main:app - Run tests:
poetry run pytest
- The Grinch has secretly broken the code and the tests are not passing...
- What are the uses cases, entities and ports? What are the primary adapters? And secondary?
use cases: createuser, listusers / entities: user ports: userrepository / primary adapters: web / secondary adapters: UserRepositoryInMemory - Let's prevent repeated emails. Here's the test:
def test_cant_create_repeated_user(): client = TestClient(WebApp(user_repository=UserRepositoryInMemory())) _create_user(client, "jake.jackson@fbi.gov", "Jake Jackson", "password") response = _create_user(client, "jake.jackson@fbi.gov", "Jake Jackson", "password") assert response.status_code == 409 assert _list_users(client).json() == [ {"name": "Jake Jackson", "email": "jake.jackson@fbi.gov"} ]
- Where did you write the error class and why?
it belongs to UserRepository port because then any of its implementations can throw it. - What does a port mean? What does it contain?A port is an abstraction of a secondary repository.
it contains its interface, DTOs (request/response models) and possible errors. - What's the advantage of the
UserRepositoryport? What would you do to create and use an alternative toUserRepositoryInMemory?
Abstracting the way we do User CRUD. We could create another implementation like UserRepoDatabase and inject it in main.py. - Let's allow deleting a user. Here's the test:
def test_delete_a_user(): client = TestClient(WebApp(user_repository=UserRepositoryInMemory())) _create_user(client, "jake.jackson@fbi.gov", "Jake Jackson", "password") _create_user(client, "john.doe@gmail.com", "John Doe", "password") response = _delete_user(client, "jake.jackson@fbi.gov") assert response.status_code == 204 assert _list_users(client).json() == [ {"name": "John Doe", "email": "john.doe@gmail.com"} ] def _delete_user(client, email: str): return client.delete(url=f"/users/{email}")
- Why do we only test "as a user"? Don't we create domain tests? What's the trade-off?
To be more realistic; to avoid testing implementation details; to ease refactoring; to document the codebase abilities. The trade-off is that we loose a bit a pinpointing ability. Tests may be slower. We may need to do variations hitting the domain directly though. - Where would you put a CLI or a worker/job? In the root.
Although they're adapters, they're primary adapters (i.e. entrypoints). That means they're the reason this app exists; therefore it's ok to give them highlight in the root folder. - Why is
main.pyunderweb?
Because it's the booting and DI of the web app primary adapter (entrypoint). Other primary adapters would have their own booting/DI. - What are the upsides/downsides of having a use-case orientation? (separating use cases per file in the web and the
domain)
More files but more clarity on the abilities of the app. Less code sharing which good so that features become modular. It makes dependencies much more clear because each feature only has what it needs. Finally, less code hotspots. - Let's create a DSL for test usage (suggestion: create an
ApiClientclass and pass it the HTTP client).