# The Shipping app tour
by Martin Gaitán, with :heart: for Shelby Team (and friends)
---
## It's just carriers!
but **done right!**

----
## The pieces
```
Api <-> Domain <-> Infra Apis
| |
domain infra
dataclasses dataclasses
(aka entities)
```
Rest/Graphql <-> Domain Service <-> Carrier's client method
----
## What's a client?
`----`
- Infra level (i.e our lowest level!)
- It should works as external library
- Speaks the "carrier api" language
- we wraps input/output data as a high-level dataclasses and enums (`carrier.types` package)
- Error codes are parsed to raise custom exceptions (semantic ftw!)
- Import in the form `from import types as api`
- Never use domain or core's shit here!
----
## What's the domain?
- High level specific service (Quoter, Labeleler, Manifester) **for each carrier**.
- Internally, each one uses the client's method of interest from the correspondet carrier.
- 2 bases: `Base<Service>` ABC + carrier specific base class for shared methods for the same carrier. (`Base<Carrier>Service`)
- The public interface is just the specific method `Quoter.get_quote`
---
### Example
From `shipping/domain/carriers/endicia/quoter.py` :
```python
class Quoter(BaseQuoter, BaseEndiciaService):
``
async def get_quote(carrier_info: CarrierInfo, ship_data: Shipment) -> Quote:
```
----
## Why dataclasses everywhere?
> 
---
### It's a lot of work
But it worths the effort

---
### A hackaton project?
I hate verbose fixtures with hardcoded dataclasses, it's too much work!
But every field is annotated, so why if with automagically get valid instances from those hints?
https://gist.github.com/mgaitan/dcbe08bf44a5af696f2af752624ac11b
---
## Stack
- Python 3.8!
- Tartiflette (instead grapiql/graphene)
- fastapi (instead flask)
- Invoke (aka fabric, for handy commands)
- `inv shipping.test` `inv shipping.shell
---
## Async what?
- Services and clients are `coroutines`
ie `async def`...
- It's a future-ready design, but
it's not *really* async everywhere for now
- We still use our known `Request maker` service in the clients (based on synchronic `requests`)
---
## So, how hook the new with the legacy?
- Under a feature flag
- convert dict-data to input Domain entities
- a tiny client to do the query/mutation to shipping app for the service
- Inverse way: convert result to the legacy dict-format
- Example: at legacy controller `@labels.route("/label/quote" ... ``
---
## Goddies
---
### Services are objects in a namespace
```python=
from shipping.services import shipping_services
warehouse_fetcher = shipping_services.core.inventory.warehouse.fetcher
```
---
### Shared fixtures
Move useful fixtures from core's to `infrastruture/tests/fixtures.py`
- They'll be available both for shiphero_app and shipping
- Consider this an external plugin. Only "general" fixtures please!
{"metaMigratedAt":"2023-06-16T00:57:40.712Z","metaMigratedFrom":"YAML","title":"The Shipping app tour","breaks":true,"slideOptions":"{\"transition\":\"slide\"}","contributors":"[{\"id\":\"5569ffea-cff4-4b2a-9cd8-81f03111492a\",\"add\":9420,\"del\":6104}]"}