Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nestify #85

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b2db9b5
pass products vs repo to server logic
electricmonk Apr 15, 2024
7304db5
introduced interfaces derived from Mongo impl
electricmonk Apr 15, 2024
6c78052
use nestjs
electricmonk Apr 15, 2024
9e66362
cleanup
electricmonk Apr 15, 2024
cad974d
fix lockfile
electricmonk Apr 19, 2024
e4ee616
fixed import bug
electricmonk Apr 19, 2024
a3dbff9
added two other varieties of module registration
electricmonk May 12, 2024
d1ec2d4
some more examples
electricmonk May 16, 2024
4e6e5c8
fixed compilation error
electricmonk May 19, 2024
90c6177
rebase on main
electricmonk May 29, 2024
427031b
extract cartManager to test injection of providers into controllers; …
electricmonk Aug 6, 2024
9496ae7
extract cartManager to test injection of providers into controllers; …
electricmonk Sep 3, 2024
afdfaf2
push search to backend
electricmonk Sep 3, 2024
fa67085
added contract test for case-insensitive search
electricmonk Sep 3, 2024
f54cb97
separated catalog, orders and cart to separate apps
electricmonk Sep 4, 2024
0f54a75
move inter-microservice calls to kafka, not working right now
electricmonk Sep 4, 2024
66d9273
kafka working in-memory
electricmonk Sep 4, 2024
6c212f1
client to use microservices too
electricmonk Sep 4, 2024
30fe8eb
e2e should use microservices
electricmonk Sep 4, 2024
032f46a
wait on proper ports
electricmonk Sep 4, 2024
0a542a5
removed superfluous OrderRepo in Cart
electricmonk Sep 4, 2024
cb636b8
removed backend abstraction layer from client
electricmonk Sep 5, 2024
25805e8
simplify
electricmonk Sep 5, 2024
38ed25f
simplify
electricmonk Sep 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ jobs:
- run: docker compose -f packages/e2e/docker-compose.yml up -d
- run: yarn dlx wait-on tcp:27017
- run: yarn dev &
- run: yarn dlx wait-on tcp:8080
- run: yarn dlx wait-on tcp:8081
- run: yarn dlx wait-on tcp:8082
- run: yarn dlx wait-on tcp:8083
- run: set -o pipefail; yarn test:ci | bash ./predate.sh
- uses: actions/upload-artifact@v3
with:
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@
"devDependencies": {
"@types/cookie-parser": "^1.4.7",
"cookie-parser": "^1.4.6",
"jsdom": "^24.1.0"
"jsdom": "^24.1.0",
"nest-memory-transport": "^1.0.3"
},
"dependencies": {
"@nestjs/config": "^3.2.2",
"@nestjs/microservices": "^10.4.1",
"kafkajs": "^2.2.4",
"winston": "^3.13.0"
}
}
36 changes: 0 additions & 36 deletions packages/client/src/adapters/backend.ts

This file was deleted.

9 changes: 0 additions & 9 deletions packages/client/src/adapters/cart.ts

This file was deleted.

27 changes: 17 additions & 10 deletions packages/client/src/adapters/context.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import React, { PropsWithChildren, useMemo } from "react";
import { HTTPShopBackend } from './backend';
import { CartAdapter } from "./cart";
import { OrderAdapter } from "./order";
import { ProductCatalog } from "./productCatalog";
import axios, {AxiosInstance} from "axios";

type Adapters = {
cart: CartAdapter;
productCatalog: ProductCatalog;
orders: OrderAdapter;
cart: AxiosInstance;
productCatalog: AxiosInstance;
orders: AxiosInstance;
}

export const IOContext = React.createContext<Adapters>(undefined as unknown as Adapters);

export const IOContextProvider: React.FC<PropsWithChildren<{backendUrl: string}>> = ({backendUrl, children}) => {
const backend = useMemo(() => new HTTPShopBackend(backendUrl), [backendUrl]);
return <IOContext.Provider value={{cart: backend, productCatalog: backend, orders: backend}}>{children}</IOContext.Provider>;
export const MonolithIOProvider: React.FC<PropsWithChildren<{backendUrl: string}>> = ({backendUrl, children}) => {
const client = useMemo(() => axios.create({ baseURL: backendUrl }), [backendUrl])

return <IOContext.Provider value={{cart: client, productCatalog: client, orders: client}}>{children}</IOContext.Provider>;
}

type Props = PropsWithChildren<{catalogUrl: string, cartUrl: string, ordersUrl: string}>;
export const MicroservicesIOProvider: React.FC<Props> = ({catalogUrl, cartUrl, ordersUrl, children}) => {
const cart = useMemo(() => axios.create({ baseURL: cartUrl }), [cartUrl])
const productCatalog = useMemo(() => axios.create({ baseURL: catalogUrl }), [catalogUrl])
const orders = useMemo(() => axios.create({ baseURL: ordersUrl }), [ordersUrl])

return <IOContext.Provider value={{cart, productCatalog, orders}}>{children}</IOContext.Provider>;
}
72 changes: 54 additions & 18 deletions packages/client/src/adapters/harness.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
import {render, within} from "@testing-library/react";
import {InMemoryOrderRepository, InMemoryProductRepository} from "@ts-react-tdd/server/src/adapters/fakes";
import {createServerLogic} from "@ts-react-tdd/server/src/server";
import {createTestingModule, runMicroservices} from "@ts-react-tdd/server/src/server.testkit";
import {QueryClient, QueryClientProvider} from "react-query";
import {MemoryRouter} from "react-router-dom";
import {App} from "../components/App";
import {IOContextProvider} from "./context";
import {MicroservicesIOProvider, MonolithIOProvider} from "./context";
import userEvent from "@testing-library/user-event";
import {ProductTemplate} from "@ts-react-tdd/server/src/types";

type AppContext = {
productRepo?: InMemoryProductRepository,
orderRepo?: InMemoryOrderRepository
products: ProductTemplate[]
};

export async function makeApp({
productRepo = new InMemoryProductRepository(),
orderRepo = new InMemoryOrderRepository()
}: AppContext) {
const fastify = createServerLogic(productRepo, orderRepo);
const queryClient = new QueryClient();

const baseUrl = await fastify.listen({host: '127.0.0.1', port: 0});

const app = render(<MemoryRouter><IOContextProvider backendUrl={baseUrl}> <QueryClientProvider client={queryClient}><App/></QueryClientProvider></IOContextProvider>
</MemoryRouter>);
function createDriver(app: ReturnType<typeof render>) {

const addProductToCart = async (title: string) => {
const product = await app.findByLabelText(title)
Expand All @@ -42,20 +31,67 @@ export async function makeApp({
await userEvent.click(app.getByRole('button', { name: /home/i }));
}

const driver = {
return {
...app,
addProductToCart,
viewCart,
checkout,
home
};

}

export async function makeMonolithicApp({
products = [],
}: AppContext) {

const {nest, orderRepo, productRepo} = await createTestingModule(products);

const queryClient = new QueryClient();

const server = await nest.listen(0, "127.0.0.1");

const app = render(<MemoryRouter><MonolithIOProvider backendUrl={await nest.getUrl()}> <QueryClientProvider client={queryClient}><App/></QueryClientProvider></MonolithIOProvider>
</MemoryRouter>);

const driver = createDriver(app);

return {
productRepo,
orderRepo,
driver,
[Symbol.dispose]: () => fastify.close(),
[Symbol.dispose]: () => server.close(),
};
}

export async function runBackendAndRender({
products = [],
}: AppContext) {

const { catalogApp, ordersApp, cartApp, orderRepo } = await runMicroservices(products)

const queryClient = new QueryClient();

const catalogServer = await catalogApp.listen(0, "127.0.0.1");
const ordersServer = await ordersApp.listen(0, "127.0.0.1");
const cartServer = await cartApp.listen(0, "127.0.0.1");

const app = render(<MemoryRouter>
<MicroservicesIOProvider cartUrl={await cartApp.getUrl()} catalogUrl={await catalogApp.getUrl()} ordersUrl={await ordersApp.getUrl()}>
<QueryClientProvider client={queryClient}><App/></QueryClientProvider>
</MicroservicesIOProvider>
</MemoryRouter>);

const driver = createDriver(app);

return {
orderRepo,
app: driver,
[Symbol.dispose]: async () => {
await cartServer.close();
await catalogServer.close();
await ordersServer.close();
},
};
}

Expand Down
6 changes: 0 additions & 6 deletions packages/client/src/adapters/order.ts

This file was deleted.

6 changes: 0 additions & 6 deletions packages/client/src/adapters/productCatalog.ts

This file was deleted.

18 changes: 13 additions & 5 deletions packages/client/src/components/Shop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import React, {useState} from "react";
import {useProducts} from "../hooks/products";
import {useCartWidget} from "../hooks/cart";

interface ShopProps {
type ShopProps = {
cartId: string;
}

export const Shop: React.FC<ShopProps> = ({ cartId }) => {

const [ freeTextSearch, setFreeTextSearch ] = useState('');
const { products, productsLoading, productsError } = useProducts({freeTextSearch});
const products = useProducts(freeTextSearch);
const { viewCart, addItem, itemCount, fetched } = useCartWidget(cartId);

return <section>
Expand All @@ -20,11 +20,19 @@ export const Shop: React.FC<ShopProps> = ({ cartId }) => {
<input type="text" aria-label="free-text-search" placeholder="Search products" value={freeTextSearch} onChange={(e) => setFreeTextSearch(e.target.value)}/>
<button aria-label="Search">Search</button>
</section>
<Products addItem={addItem} products={products} isLoading={productsLoading} error={productsError} />
<Products addItem={addItem} products={products}/>
</section>
};

const Products: React.FC<{ products: Product[] | undefined, isLoading: boolean, error: unknown | null, addItem: (id: string) => void }> = ({ products, isLoading, error, addItem }) => {
type ProductsProps = {
products: {
data: Product[] | undefined;
isLoading: boolean;
error: unknown | null;
}
addItem: (id: string) => void;
}
const Products: React.FC<ProductsProps> = ( {products: {data, isLoading, error}, addItem}) => {

if (isLoading) {
return <section>Loading...</section>
Expand All @@ -34,7 +42,7 @@ const Products: React.FC<{ products: Product[] | undefined, isLoading: boolean,
return <section><>Error: {error}</></section>
}

return <>{products!.map(({ title, id }) =>
return <>{data!.map(({ title, id }) =>
<div key={id} aria-label={title}>
<h3>{title}</h3>
<button onClick={() => addItem(id)} aria-label="Add to cart" role="button">
Expand Down
Loading
Loading