Coding along with the Udemy course:
- Microservices with Node JS and React
Instructor: Stephen Grider- Docker Google Cloud Kubernetes, Jest, MongoDB, NATS Streaming Server, Next JS, Node JS, NPM, Skaffold, React, TypeScript
- create the Kubernetes Cluster in Google Cloud
- run the Connect command provided
- generate secrets:
kubectl create secret generic jwt-secret --from-literal=JWT_KEY=xyz123
kubectl create secret generic stripe-secret --from-literal=STRIPE_KEY=abc123
- Run two commands to install the Ingress-Nginx Controller:
kubectl create clusterrolebinding cluster-admin-binding \ --clusterrole cluster-admin \ --user $(gcloud config get-value account)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml
skaffold dev
- wait, the process takes a few minutes to complete
- get the ingress IP address
- apply that IP locally to the
/etc/hosts
file for the domain you want to subtitute, in this case,ticketing.com
Route | Goal |
---|---|
/auth/signin | Signin form |
/auth/signup | Signup form |
/auth/signout | Log out |
/ | List all tickets ^new^ |
/tickets/new | New ticket ^new^ |
/tickets/:ticketId | Ticket details ^new^ |
/orders/:orderId | Order details, Payment button ^new^ |
- for Next route files which intake a parameter which the component utilizes, ie:
/tickets/[ticketId].js
Payment Service |
---|
charges |
orders |
Listen | Publish |
---|---|
order:created | charge:created |
order:cancelled |
- payments service will receive order created event, and listens for order cancelled
- emits a charge created event
- Stripe integrated for transactions
- cancelled event emit/publish added
- tests and NATS mocks added
-
expiration date,
isReserved()
promise added to ticket model, -
Order
route method body purpose /api/orders GET - all active orders for current user /api/orders/id GET - get order details /api/orders POST {ticketID: string}
create order for ticket# /api/orderes/:id DELETE - delete order# prop type userId the user who created status expired, paid, pending
expiresAt order ttl timestamp ticketId id
of ticketTicket
prop type title title
of eventprice price
in USDversion don't process events twice
- env vars created in Tickets YAML deployment for NATS values
- NATS wrapper/singleton, graceful shutdown
-
initial stages of trying out publishing and listening to events using NATS Streaming Server (deprecated), Common updated with Events code
-
Class Listener
Property Type Goal subject string name of the channel for listener onMessage (event: EventData)=>void
run when message received client Stan pre-initialized NATS client queueGroupName string name of queue group listener will join ackWait number seconds has to ack message subscriptionOptions SubscriptionOptions default subscription options listen ()=>void
sets up subscription parseMessage (msg:Message)=>any helper for parsing messages -
Queue Group assigned to listeners - when we have multiple instances of a listener (for load balancing), new messages will be sent to only one listener instances so concurrent responses are not triggered
-
port fowarding for the node port service, ie.
kubectl port-forward nats-deployment-xxx 4222:4222
Route | Method | Body | Goal |
---|---|---|---|
/api/tickets | GET | - | Retrieve all tickets |
/api/tickets/:id | GET | - | Retrieve one ticket |
/api/tickets | POST | {title: string, price: string} |
Create ticket |
/api/tickets | PUT | {title: string, price: string} |
Update ticket |
- utilized
MONGO_URI
environment variable defined in thedepl.yaml
for Auth and Tickets for the connection string
- shared NPM library created from Auth souces
/middleware
and/errors
- Organization created in NPM, and module published as
@agrtickets/common
useEffect
used in SignOut for the/api/users/signout
reqeust
-
once a
getInitialProps
is also added to our customAppComponent
(_app.js
), thegetInitialProps
inindex.js
no longer gets called. This is resolved by callingappContext.Component.getInitialProps
-
nested context props when a Custom App Component is used
getInitialProps
context Page Component context==={req, res}
Custom App Component context==={Component, ctx: {req, res}}
-
AppComponent full context:
[ 'AppTree', 'Component', 'router', 'ctx' ]
- reminder https when testing the cookie ๐
- resolved
Error: connect ECONNREFUSED 127.0.0.1:80
for when we call/api/users/currentuser
. We're calling a service not in our Client or Next container, so the call is not getting routed to the Ingress Nginx Controller. This can be mitigated usinggetInitialProps
- getInitialProps is invoked at specific times as per Next
request source | getInitialProps execution |
---|---|
page hard refresh | server |
clicking link from different domain | server |
typing URL in address bar | server |
navigating in app between pages | client |
apiBuildClient
created to handle these requests from inside or outside of the container, a call to the Ingress controller (not our React client) we route thebaseURL
to the ingress controller's name (usekubectl get services -n ingress-nginx
) and the ingress' namespace (usekubectl get namespace
). In this case, the configured path ishttp://ingress-nginx-controller.ingress-nginx.svc.cluster.local
- instead of setting up the post call manually for each network request, a hook has been added so once we define the
url, method, body
, simply calluseRequest()
npm install bootstrap
npm install axios
- for making the http requests (POST, GET, etc.)- Signup form started which utilizes the custom error responses developed earlier for a valid email address and password
- Next added for server side React rendering, Client Deployment and Service are working in cluster
- additional authentication tests added
- tests
setup
set with a global namespace entry for reuse whenever asignin()
step is needed during unit tests
npm install --save-dev @types/jest @types/supertest jest ts-jest supertest mongodb-memory-server
-- so these aren't deployed to the cluster, in the Dockerfile--omit=dev
- refactored so we can test
app
solo, and utilize the ephemeral ports the testing library gives us (ie, we cannot use port 3000 to testapp
) - Jest setup added, one passing test
UnauthorizedError
added,requireAuthentication
check added and testing successfully in thecurrentUserRouter
route (cannot access the path unless user is logged in)
- added a shared
currentUserCheck
(verify JWT enviroment variable) which includes usingdeclare global
to modify an existing type definition, in this case, Express'Request
to add acurrentUser
property - VS Code preference to dismiss the
yaml
linting warning aboutOne or more containers do not have resource limits
, use the following insetting.json
:
"vs-kubernetes": { "disable-linters": ["resource-limits"], ... },
- setting the
session
tonull
destroys the cookie
- establish if user has a cookie with a valid JWT
- using same pattern of session JWT coded in SignUp
- comparision helper method in PasswordManager for stored and supplied password
- session cookie is authored when a User is created (
npm install cookie-session
,npm install jsonwebtoken
and the@types
), JSON web token value is written in the cookie - Kubernetes Secret is utilized for the JWT key
- secret creation example
kubectl create secret generic jwt-secret-name --from-literal=JWT_KEY=xxxxx
- generate 32 bit string:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
- before writing to MongoDB,
password
is not longer saved in plain text (login comparison is a later step)
- existing email check, added
BadRequestError
that can be used withthrow
- created interfaces for mongoose/TypeScript, used those types in the generic definitions, so now we can call User.build({}) and get proper type checking along the way
- installed npm package
express-async-errors
to alter the default route handling ofExpress
, there will be anawait
added to anasync
route (so we don't have to be confined to usingnext()
)
- Error class extended: RequestValidationError, DatabaseConnectionError
- custom errors created, using them in route handler, common formatted response
{errors: {message: string, field?: string}[]}
- integral are the steps needed for deploying an Ingress Controller and GKE
- added config file for trying out Skaffold, seems to kick off faster than possible and inconsistent services fail during deployment, todo/research
- need to understand/resolve ingress to the client, data is displaying (incorrect)
- resolving exising issue of two routes, a GET and a POST, both to
/posts
, ngnix cannot do routing based on the request method type, so we have to revise to unique paths - for an ingress controller, use the
apply
command noted in the nginx ingress controller documentation (see "If you don't have Helm..."). When done successfullykubectl get ingress
will display an IP under 'ADDRESS' and requets tohttp://posts.com/posts
result in JSON entries posted via postman, usegcloud compute instances list
to get an external IP for the postman request address, andkubectl describe services
to find the random port 3xxxx port assigned - added an entry in local
/etc/hosts
file to redirectposts.com
requests to the IP of the Ingress controller - moved all infrastructure files into one folder, thus
kubectl apply -f .
to kick them all off
-
integrated Cluster IP services for the event-bus and posts applications, tested using K8s Service names instead of
localhost
addresses -
having memory and cpu limits in deployment file caused 'minimum cpu' errors when deploying and caused
Pending
instances, commented out generated defaults
Testing / utilizing configuration files
- even though the
kubectl k get services
ouput reads as if a 3xxxx port is available, a firewall rule must be added in Google Cloud to open that port to traffic, ie.:gcloud compute firewall-rules create test-node-port \ --allow tcp:30317
- Google Cloud docs, eposing apps
Service | communication | destination |
---|---|---|
Posts | <=> | Event Bus |
Comments | <=> | "" |
Query | <=> | "" |
Moderation | <=> | "" |
-
CommentModerated event
- id : string
- content : string
- postId : string
- status : 'approved' | 'rejected'
-
emitted from the Moderation service
-
received inside of Comments service
-
Added:
Service | local folder | port |
---|---|---|
Moderation | /moderation | 4003 |
Bringing up new services surfaces the strategy of storing events, to run new service/event jobs on previous events. Enter, the Event Sync changes:
- Store event bus events inside of an array
- Add endpoint to Event Bus to retrieve all events that have occurred
- Make sure when Query service is launched it reaches out to the Event Bus to request all events, and the Quer service tries to process that data
There's no persistence here, the exercise is the communication between resources and running our moderation sercie
Definitely messy business adding or changing events ๐ฃ
Service | communication | destination |
---|---|---|
Posts | <=> | Event Bus |
Comments | <=> | "" |
Query | <=> | "" |
-
The application example is that of a blog (React front-end, Node microservices) where there can be posts with many comments, the comments will then have an approval flow.
-
Event bus example using Express coded by hand and working. The design will not have the vast majority of features a normal bus has. Production grade version will be in a later stage. The Posts, Comments, and Query services all communicate through the event bus.
-
Services:
Service local folder port React /client 3000 Posts /posts 4000 Comments /comments 4001 Query /query 4002 Event Bus /event-bus 4005 -
React project structure
- App
- PostList
- CommentsList
- CommentsCreate
- PostCreate
- PostList
- App