# The Shipping app tour by Martin Gaitán, with :heart: for Shelby Team (and friends) --- ## It's just carriers! but **done right!** ![image](https://user-images.githubusercontent.com/2355719/119516235-f9244500-bd4c-11eb-94e9-e049422601b0.png) ---- ## 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? > ![image](https://user-images.githubusercontent.com/2355719/119516850-7b146e00-bd4d-11eb-9cb3-83f9c7112097.png) --- ### It's a lot of work But it worths the effort ![image](https://user-images.githubusercontent.com/2355719/119516235-f9244500-bd4c-11eb-94e9-e049422601b0.png) --- ### 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}]"}
    108 views