Original link: Ewan Valentine. IO, translation is authorized by author Ewan Valentine.

The full code for this article: GitHub

In the previous section, we used Go-Micro to build an event-driven architecture for microservices. This section reveals how to call and interact with microservices from a Web client perspective.

Microservices interact with the Web side

Referring to the Go-Micro documentation, you can see that Go-Micro implements a mechanism for requesting RPC methods for web client proxies.

Internal calls

The method that microservice A calls microservice B needs to be instantiated and then called: bclient.callrpc (args…) The data is passed as a parameter and is an internal call.

Outside calls

The Web browser calls the microservice method through HTTP request, while Go-Micro serves as the middle layer. The data of calling method is submitted in the form of HTTP request, which belongs to external call.

REST vs RPC

The REST style has dominated the Web development landscape for many years, often used for resource management on both the client and the server, with a much wider range of applications than RPC or SOAP. More references: Zhihu: Comparison between RPC and RESTful apis

REST

The REST style of resource management is simple and standardized, it corresponds to the HTTP request method to add, delete, change and query resources, and can also use HTTP error code to describe the response status, in most Web development REST is an excellent solution.

RPC

In recent years, however, the RPC style has also been gaining popularity with microservices. REST is good for managing different resources simultaneously, but while microservices typically focus on managing a single resource, using the RPC style allows Web development to focus on the implementation and interaction of individual microservices.

Micro toolkit

We’ve been using the Go-Micro framework since Section 2, and now look at its API gateway. Go-micro provides an API gateway to proxy microservices. The API gateway proxy the micro service RPC method into web request, and open up the URL used by the Web side

use

# Install go-Micro toolkit: # $go get -u github.com/micro/micro # We directly use its Docker image $Docker pull MicroHQ/MicroCopy the code

Now modify the user-service code:

Package main import ("log" pb "shippy/user-service/proto/user" // github.com/micro/go-micro") // Connect to the database db, err := CreateConnection() defer db.close () if err! = nil { log.Fatalf("connect error: %v\n", Err)} repo := &userRepository {db} db.automigrate (&pb.user {} // Change shippy.auth to go.micro. Srv. user // change API call parameter SRV := micro.  micro.Name("go.micro.srv.user"), micro.Version("latest"), Srv.init () // Get broker instance // pubSub := s.server ().options ().broker publisher := micro. srv.Client()) t := TokenService{repo} pb.RegisterUserServiceHandler(srv.Server(), &handler{repo, &t, publisher}) if err := srv.Run(); err ! = nil { log.Fatalf("user service error: %v\n", err) } }Copy the code

Shippy-user-service /tree/tutorial-6

API gateway

Now make run user-service and mil-service as in the previous section. Then execute:

$ docker run -p 8080:8080 \ 
             -e MICRO_REGISTRY=mdns \
             microhq/micro api \
             --handler=rpc \
             --address=:8080 \
             --namespace=shippy
Copy the code

API Gateway now runs on port 8080 and tells it to use MDNS for service discovery like other microservices, and finally to use the namespace shippy, which will prefix our service names like Shippy.auth, Shippy.email, The default value is go.micro. API. Using the default value without specifying it will not take effect.

Create a user on the Web

External users can now call user-service methods like this:

$ curl -XPOST -H 'Content-Type: application/json' \
    -d '{
            "service": "shippy.auth",
            "method": "Auth.Create",
            "request": {
                "user": {
                    "email": "[email protected]",
                    "password": "testing123",
                    "name": "Ewan Valentine",
                    "company": "BBC"
                }
            }
	}' \ 
    http://localhost:8080/rpc
Copy the code

The effect is as follows:

In this HTTP request, we give the parameters of the User-service Create method as JSON field values, which the API gateway automatically calls for us and returns the result of the method processing in JSON format as well.

Authenticated users on the Web

$ curl -XPOST -H 'Content-Type: application/json' \ 
    -d '{
            "service": "shippy.auth",
            "method": "Auth.Auth",
            "request": {
                "email": "[email protected]",
                "password": "SomePass"
            }
	}' \
    http://localhost:8080/rpc
Copy the code

The running effect is as follows:

The user interface

Now use the React create-app library to make web calls to the above API. $NPM install -g react-create-app install -g react-create-app shippy-ui

App.js

// shippy-ui/src/App.js import React, { Component } from 'react'; import './App.css'; import CreateConsignment from './CreateConsignment'; import Authenticate from './Authenticate'; class App extends Component { state = { err: null, authenticated: false, } onAuth = (token) => { this.setState({ authenticated: true, }); } renderLogin = () => { return ( <Authenticate onAuth={this.onAuth} /> ); } renderAuthenticated = () => { return ( <CreateConsignment /> ); } getToken = () => { return localStorage.getItem('token') || false; } isAuthenticated = () => { return this.state.authenticated || this.getToken() || false; } render() { const authenticated = this.isAuthenticated(); return ( <div className="App"> <div className="App-header"> <h2>Shippy</h2> </div> <div className='App-intro container'>  {(authenticated ? this.renderAuthenticated() : this.renderLogin())} </div> </div> ); } } export default App;Copy the code

Next, add two components for user authentication and shipment.

Authenticate Indicates the user authentication component

// shippy-ui/src/Authenticate.js import React from 'react'; class Authenticate extends React.Component { constructor(props) { super(props); } state = { authenticated: false, email: '', password: '', err: '', } login = () => { fetch(`http://localhost:8080/rpc`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ request: { email: this.state.email, password: This.state. password,}, // Note that Auth is a separate project from go.micro-srv.user. 'shippy.auth', method: 'Auth.Auth', }), }) .then(res => res.json()) .then(res => { this.props.onAuth(res.token); this.setState({ token: res.token, authenticated: true, }); }) .catch(err => this.setState({ err, authenticated: false, })); } signup = () => { fetch(`http://localhost:8080/rpc`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ request: { email: this.state.email, password: this.state.password, name: this.state.name, }, method: 'Auth.Create', service: 'shippy.auth', }), }) .then((res) => res.json()) .then((res) => { this.props.onAuth(res.token.token); this.setState({ token: res.token.token, authenticated: true, }); localStorage.setItem('token', res.token.token); }) .catch(err => this.setState({ err, authenticated: false, })); } setEmail = e => { this.setState({ email: e.target.value, }); } setPassword = e => { this.setState({ password: e.target.value, }); } setName = e => { this.setState({ name: e.target.value, }); } render() { return ( <div className='Authenticate'> <div className='Login'> <div className='form-group'> <input type="email" onChange={this.setEmail} placeholder='E-Mail' className='form-control' /> </div> <div className='form-group'> <input type="password" onChange={this.setPassword} placeholder='Password' className='form-control' /> </div> <button className='btn btn-primary' onClick={this.login}>Login</button> <br /><br /> </div> <div className='Sign-up'> <div className='form-group'> <input type='input' onChange={this.setName} placeholder='Name' className='form-control' /> </div> <div className='form-group'> <input type='email' onChange={this.setEmail} placeholder='E-Mail' className='form-control' /> </div> <div className='form-group'> <input type='password' onChange={this.setPassword} placeholder='Password' className='form-control' /> </div> <button className='btn btn-primary' onClick={this.signup}>Sign-up</button> </div> </div> ); } } export default Authenticate;Copy the code

CreateConsignment Cargo shipping component

// shippy-ui/src/CreateConsignment.js import React from 'react'; import _ from 'lodash'; class CreateConsignment extends React.Component { constructor(props) { super(props); } state = { created: false, description: '', weight: 0, containers: [], consignments: [], } componentWillMount() { fetch(`http://localhost:8080/rpc`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ service: 'shippy.consignment', method: 'ConsignmentService.Get', request: {}, }) }) .then(req => req.json()) .then((res) => { this.setState({ consignments: res.consignments, }); }); } create = () => { const consignment = this.state; fetch(`http://localhost:8080/rpc`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ service: 'shippy.consignment', method: 'ConsignmentService.Create', request: _.omit(consignment, 'created', 'consignments'), }), }) .then((res) => res.json()) .then((res) => { this.setState({ created: res.created, consignments: [...this.state.consignments, consignment], }); }); } addContainer = e => { this.setState({ containers: [...this.state.containers, e.target.value], }); } setDescription = e => { this.setState({ description: e.target.value, }); } setWeight = e => { this.setState({ weight: Number(e.target.value), }); } render() { const { consignments, } = this.state; return ( <div className='consignment-screen'> <div className='consignment-form container'> <br /> <div className='form-group'> <textarea onChange={this.setDescription} className='form-control' placeholder='Description'></textarea> </div> <div className='form-group'> <input onChange={this.setWeight} type='number'  placeholder='Weight' className='form-control' /> </div> <div className='form-control'> Add containers... </div> <br /> <button onClick={this.create} className='btn btn-primary'>Create</button> <br /> <hr /> </div> {(consignments && consignments.length > 0 ? <div className='consignment-list'> <h2>Consignments</h2> {consignments.map((item) => ( <div> <p>Vessel id: {item.vessel_id}</p> <p>Consignment id: {item.id}</p> <p>Description: {item.description}</p> <p>Weight: {item.weight}</p> <hr /> </div> ))} </div> : false)} </div> ); } } export default CreateConsignment;Copy the code

The full code for the UI can be seen: shippy-UI

Now execute NPM start with the following effect:

Open Chrome’s Application to see the RPC successfully called during registration or login:

conclusion

This section uses go-Micro’s own API gateway to complete the invocation of micro-service functions on the Web side. It can be seen that the input and output parameters of function parameters are given in JSON, corresponding to the first section of Protobuf which says that JSON is only selected for interaction with the browser. In addition, there are a few differences between the author code and the translator code, please note, thank you.

In the next section we will introduce the Google Cloud platform to host our microservice projects and manage them using Terraform.