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

39 Moving the Front end application from Go to Next.js #236

Merged
merged 30 commits into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3e873e2
39 adding the next.js frontend app to the code base
xoscar Jul 22, 2022
290c7b8
39 adding the next.js frontend app to the code base
xoscar Jul 26, 2022
7213709
39 adding the next.js frontend app to the code base
xoscar Jul 26, 2022
e71da1a
39 adding the next.js frontend app to the code base
xoscar Jul 26, 2022
439f795
39 adding the next.js frontend app to the code base
xoscar Jul 26, 2022
e63d9fb
Merge branch 'main' into 39-web-application
xoscar Jul 26, 2022
96bbd2c
Merge branch 'main' into 39-web-application
xoscar Jul 27, 2022
3a7b71f
39 removing the deployment details
xoscar Jul 27, 2022
e610494
39 PR updates
xoscar Aug 1, 2022
e403697
Merge branch 'main' into 39-web-application
xoscar Aug 4, 2022
15a0dc6
39 removing the protos folder
xoscar Aug 4, 2022
40135ab
39 fixing multiple minor issues
xoscar Aug 4, 2022
1780b53
39 updating frontend README
xoscar Aug 8, 2022
510d47b
Merge branch 'main' into 39-web-application
xoscar Aug 8, 2022
9d1fede
39 removing unnecessary images
xoscar Aug 8, 2022
19c9cc1
39 fixing ssr bug
xoscar Aug 8, 2022
feaa207
39 fixing ssr bug
xoscar Aug 8, 2022
afb9b75
39 fixing readme lint issues
xoscar Aug 8, 2022
deb5cc8
39 fixing readme lint issues
xoscar Aug 8, 2022
54444cf
39 updating the changelog
xoscar Aug 8, 2022
333a819
39 fixing readme lint issues
xoscar Aug 8, 2022
2ee1a43
39 fixing readme lint issues
xoscar Aug 8, 2022
970d4de
39 fixing multiple minor issues
xoscar Aug 8, 2022
19ea78d
39 fixing multiple minor issues
xoscar Aug 8, 2022
b1ff6c5
39 fixing product price
xoscar Aug 8, 2022
202d656
39 updatig the frontend port to 8080
xoscar Aug 9, 2022
9cdba1c
39 updating the changelog entry
xoscar Aug 9, 2022
9d79048
39 updated PR comments
xoscar Aug 9, 2022
673479a
39 cleaning up PR details
xoscar Aug 9, 2022
69e4e07
Merge branch 'main' into 39-web-application
xoscar Aug 9, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ CART_SERVICE_ADDR=cartservice:${CART_SERVICE_PORT}
CHECKOUT_SERVICE_PORT=5050
CHECKOUT_SERVICE_ADDR=checkoutservice:${CHECKOUT_SERVICE_PORT}

CURRENCY_SERVICE_PORT=7000
xoscar marked this conversation as resolved.
Show resolved Hide resolved
CURRENCY_SERVICE_PORT=7001
CURRENCY_SERVICE_ADDR=currencyservice:${CURRENCY_SERVICE_PORT}

EMAIL_SERVICE_PORT=6060
Expand All @@ -54,3 +54,5 @@ PROMETHEUS_SERVICE_PORT=9090

# Grafana
GRAFANA_SERVICE_PORT=3000

ENV_PLATFORM=local
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,11 @@ obj/
.idea/
build/
node_modules/
src/shippingservice/target/
src/shippingservice/target/

coverage
.next/
out/
build
src/frontend/protos
next-env.d.ts
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,5 @@ significant modifications will be credited to OpenTelemetry Authors.
([#260](https://github.com/open-telemetry/opentelemetry-demo/pull/260))
* Added span attributes to currency service
([#265](https://github.com/open-telemetry/opentelemetry-demo/pull/265))
* Reimplemented Frontend app using [Next.js](https://nextjs.org/) Browser client
([#236](https://github.com/open-telemetry/opentelemetry-demo/pull/236))
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ services:
ports:
- "${FRONTEND_PORT}:${FRONTEND_PORT}"
environment:
- PORT=${FRONTEND_PORT}
xoscar marked this conversation as resolved.
Show resolved Hide resolved
- FRONTEND_ADDR
- AD_SERVICE_ADDR
- CART_SERVICE_ADDR
Expand All @@ -169,6 +170,9 @@ services:
- RECOMMENDATION_SERVICE_ADDR
- SHIPPING_SERVICE_ADDR
- OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
- OTEL_RESOURCE_ATTRIBUTES=service.name=frontend
- OTEL_EXPORTER_OTLP_ENDPOINT
- ENV_PLATFORM
- OTEL_SERVICE_NAME=frontend
depends_on:
- adservice
Expand Down
26 changes: 26 additions & 0 deletions src/frontend/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
xoscar marked this conversation as resolved.
Show resolved Hide resolved
"extends": ["plugin:react/recommended", "plugin:@typescript-eslint/recommended", "next/core-web-vitals"],
"plugins": ["@typescript-eslint"],
"root": true,
"globals": {},
"rules": {
"@typescript-eslint/no-non-null-assertion": "off",
"react-hooks/exhaustive-deps": "warn",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
"max-len": [
"error",
{
"code": 150,
"ignoreComments": true,
"ignoreTrailingComments": true,
"ignoreUrls": true,
"ignoreStrings": true,
"ignoreTemplateLiterals": true
}
]
},
"parser": "@typescript-eslint/parser",
"env": {},
"overrides": []
}
6 changes: 6 additions & 0 deletions src/frontend/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.idea
.git
build
dist
.husky
node_modules
15 changes: 15 additions & 0 deletions src/frontend/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"singleQuote": true,
"arrowParens": "avoid",
"bracketSpacing": true,
"semi": true,
"trailingComma": "es5",
"printWidth": 120,
"jsxBracketSameLine": false,
"proseWrap": "always",
"quoteProps": "as-needed",
"tabWidth": 2,
"useTabs": false
}
84 changes: 40 additions & 44 deletions src/frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,44 +1,40 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM golang:1.17.7-alpine AS builder

RUN apk add build-base protoc

WORKDIR /usr/src/app/

# Restore dependencies
COPY ./src/frontend/ ./
COPY ./pb/ ./proto/
RUN go mod download
RUN go get google.golang.org/protobuf/cmd/protoc-gen-go
RUN go get google.golang.org/grpc/cmd/protoc-gen-go-grpc

# Build executable
RUN protoc -I ./proto/ ./proto/demo.proto --go_out=./ --go-grpc_out=./
RUN go build -o /go/bin/frontend/ ./

# -----------------------------------------------------------------------------

FROM alpine

RUN apk add busybox-extras net-tools bind-tools
WORKDIR /usr/src/app/

COPY --from=builder /go/bin/frontend/ ./
COPY ./src/frontend/templates/ ./templates/
COPY ./src/frontend/static/ ./static/

EXPOSE ${FRONTEND_PORT}
ENTRYPOINT [ "./frontend" ]
FROM node:16-alpine AS deps
RUN apk add --no-cache libc6-compat

WORKDIR /app

COPY ./src/frontend/package*.json ./
RUN npm ci

FROM node:16-alpine AS builder
RUN apk add --no-cache libc6-compat protoc
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY ./pb ./pb
COPY ./src/frontend .

RUN npm run grpc:generate
RUN npm run build

FROM node:16-alpine AS runner
WORKDIR /app
RUN apk add --no-cache protoc

ENV NODE_ENV=production

RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

ENV PORT 8080
EXPOSE ${PORT}
xoscar marked this conversation as resolved.
Show resolved Hide resolved

CMD ["node", "server.js"]
136 changes: 16 additions & 120 deletions src/frontend/README.md
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,131 +1,27 @@
# Frontend service
xoscar marked this conversation as resolved.
Show resolved Hide resolved
xoscar marked this conversation as resolved.
Show resolved Hide resolved

The **frontend** service is responsible for rendering the UI for the store's website.
It serves as the main entry point for the application routing requests to their
appropriate backend services.
The application uses Server Side Rendering (SSR) to generate HTML consumed by
the clients, which could be web browsers, web crawlers, mobile clients or something
else.
The frontend is a [Next.js](https://nextjs.org/) application that is composed
by two layers.

## OpenTelemetry instrumentation
1. Client side application. Which renders the components for the OTEL webstore.
2. API layer. Connects the client to the backend services by exposing REST endpoints.

### Initialization
## Build Locally

The OpenTelemetry SDK is initialized in `main` using the `InitTraceProvider` function.
By running `docker compose up` at the root of the project you'll have access to the
frontend client by going to <http://localhost:8080/>.

```go
func InitTracerProvider() *sdktrace.TracerProvider {
ctx := context.Background()
## Local development

exporter, err := otlptracegrpc.New(ctx)
if err != nil {
log.Fatal(err)
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithBatcher(exporter),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
return tp
}
```

Services should call `TraceProvider.Shutdown()` when the service is shutdown to
ensure all spans are exported.
This service makes that call as part of a deferred function in `main`.

```go
// Initialize OpenTelemetry Tracing
tp := InitTracerProvider()
defer func() {
if err := tp.Shutdown(context.Background()); err != nil {
log.Printf("Error shutting down tracer provider: %v", err)
}
}()
```

### HTTP instrumentation

This service receives HTTP requests, controlled by the gorilla/mux Router.
The following routes are defined by the frontend:

| Path | Method | Use |
|-------------------|--------|-----------------------------------|
| `/` | GET | Main index page |
| `/cart` | GET | View Cart |
| `/cart` | POST | Add to Cart |
| `/cart/checkout` | POST | Place Order |
| `/cart/empty` | POST | Empty Cart |
| `/logout` | GET | Logout |
| `/product/{id}` | GET | View Product |
| `/setCurrency` | POST | Set Currency |
| `/static/` | * | Static resources |
| `/robots.txt` | * | Search engine response (disallow) |
| `/_healthz` | * | Health check (ok) |

These requests are instrumented in the main function as part of the router's definition.

```go
// Add OpenTelemetry instrumentation to incoming HTTP requests controlled by the gorilla/mux Router.
r.Use(otelmux.Middleware("server"))
```
Currently, the easiest way to run the frontend for local development is to execute

### gRPC instrumentation

This service will issue several outgoing gRPC calls, which have instrumentation
hooks added in the `mustConnGRPC` function.

```go
func mustConnGRPC(ctx context.Context, conn **grpc.ClientConn, addr string) {
// Add OpenTelemetry instrumentation to outgoing gRPC requests
var err error
ctx, cancel := context.WithTimeout(ctx, time.Second*3)
defer cancel()
*conn, err = grpc.DialContext(ctx, addr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()),
)
if err != nil {
panic(errors.Wrapf(err, "grpc: failed to connect %s", addr))
}
}
```

### Service specific instrumentation attributes

All requests incoming to the frontend service will receive the following attributes:

- `app.session.id`
- `app.request.id`
- `app.currency`
- `app.user.id` (when the user is present)

These attributes are added in the `instrumentHandler` function (defined in the
middleware.go file) which wraps all HTTP routes specified within the
gorilla/mux router.
Additional attributes are added within each handler's function as appropriate
(ie: `app.cart.size`, `app.cart.total.price`).

Adding attributes to existing auto-instrumented spans can be accomplished by
getting the current span from context, then adding attributes to it.

```go
span := trace.SpanFromContext(r.Context())
span.SetAttributes(
attribute.Int(instr.AppPrefix+"cart.size", cartSize(cart)),
attribute.Int(instr.AppPrefix+"cart.items.count", len(items)),
attribute.Float64(instr.AppPrefix+"cart.shipping.cost", shippingCostFloat),
attribute.Float64(instr.AppPrefix+"cart.total.price", totalPriceFloat),
)
```shell
docker compose run --service-ports -e NODE_ENV=development
--volume $(pwd)/src/frontend:/app --volume $(pwd)/pb:/app/pb frontend sh
```

When an error is encountered, the current span's status code and error message
are set.
from the root folder.

```go
// set span status on error
span := trace.SpanFromContext(r.Context())
span.SetStatus(codes.Error, errMsg)
```
It will start all of the required backend services
and within the container simply run `npm run dev`.
After that the app should be available at <http://localhost:8080/>.
20 changes: 20 additions & 0 deletions src/frontend/components/Ad/Ad.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import styled from 'styled-components';
import RouterLink from 'next/link';

export const Ad = styled.section`
position: relative;
background-color: ${({ theme }) => theme.colors.otelYellow};
font-size: ${({ theme }) => theme.sizes.dMedium};
text-align: center;
padding: 48px;

* {
color: ${({ theme }) => theme.colors.white};
margin: 0;
cursor: pointer;
}
`;

export const Link = styled(RouterLink)`
color: black;
`;
18 changes: 18 additions & 0 deletions src/frontend/components/Ad/Ad.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useAd } from '../../providers/Ad.provider';
import * as S from './Ad.styled';

const Ad = () => {
const {
adList: [{ text, redirectUrl } = { text: '', redirectUrl: '' }],
} = useAd();

return (
<S.Ad>
<S.Link href={redirectUrl}>
<p>{text}</p>
</S.Link>
</S.Ad>
);
};

export default Ad;
1 change: 1 addition & 0 deletions src/frontend/components/Ad/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Ad';