Flutter - Writing The Code
This is a continuation of the initial Flutter Design. In this post, I’ll talk about writing the actual code and how it developed from an idea to a running project. When I started laying out Flutter, I wanted to make it so that each microservice was simple and self contained yet also consistent designed for modularity. I also wanted to start with a framework that I knew well and was very accessible for me. Thus I selected Java with Spring and Cassandra with Casquatch .
Service Registry
To start off this project, I needed to select a service registry. For the sake of this project, a service registry is simply a central place that all the microservices register with on startup and provides an API to round robin over them. After a bit of exploration, I selected Netflix Eureka . Eureka offers very easy integration though Spring Cloud Netflix .
Eureka Server
First step to getting going with Eureka is to spin up a server itself. This was done with a trivial spring project by adding the dependency in the pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
@EnableEurekaServer
server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
Eureka Client
Adding a Eureka Client wasn’t much more complicated when using Spring as the dependency add does the heavy lifting for you.
First, I added the dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
Added a few properties to bootstrap.properties
spring.application.name=Client-Name
server.port=0
server.servlet.context-path=/${spring.application.name}
Then added a few more to the application.properties. I did end up moving these to common module but that is optional:
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
eureka.instance.preferIpAddress=true
Client Usage
Once the client is wired up, querying Eureka is done through the EurekaClient object. First, we auto wire that up with:
@Autowired
private EurekaClient eurekaClient;
eurekaClient.getNextServerFromEureka("ServiceName",false);
eurekaClient.getNextServerFromEureka("ServiceName",true);
DAO
The DAOs all follow the same general model and thus I’ll focus on just the Follow DAO.
First there is a model which mimics the table in Cassandra as a simple mapping of two user names in this example. (See Casquatch Manual for more information on this.)
@CasquatchEntity
@Getter @Setter @NoArgsConstructor
public class Follow extends AbstractCasquatchEntity {
@PartitionKey
private String follower;
private String author;
}
The REST services exist in a class annotated with @RestController from Spring and contain simple API Wrappers from Casquatch . I started using the auto generated rest api then stripped out what I did not need. An API would look something like this
@RequestMapping(value = "/save", method= RequestMethod.POST)
public Response<Void> save(@RequestBody Request<Follow> request) {
log.trace("POST | /save | {}",request.toString());
return new Response<>(casquatchDao.save(Follow.class,request.getPayload(),request.getQueryOptions()), Response.Status.SUCCESS);
}
{"payload": {"follower":"MyUsername", "author":"OtherUsername"}}
Service
While the DAOs were simple wrappers due to Casquatch sitting beneath them. The Service layers were designed to do a little more logic as they needed to interact with each other.
As they are going to be passing around JSON objects, they needed a set of models to parse the JSON Requests / Responses. I started by having these within the microservices but this ended up being too much duplicated code and thus I rolled them in to a common module . I did use a few Abstract base classes to deduplicate the code but the models are little more than a class to parse the JSON.
The URIs for each API are derived from a simple utility function as follows:
private URI getUri(String service,String api) {
InstanceInfo instanceInfo = eurekaClient.getNextServerFromEureka(service.toUpperCase(),false);
return URI.create(String.format("http://%s:%s/%s%s",instanceInfo.getIPAddr(),instanceInfo.getPort(),service.toLowerCase(),api));
}
A service API then looks something like the following:
@RequestMapping(value = "/get/{id}", method=RequestMethod.GET)
public ResponseEntity<Message> getMessage(@PathVariable String id) {
log.trace("GET | /get/{}",id);
try {
ResponseEntity<MessageResponse> entity = restTemplate.postForEntity(getUri("message-dao","/get"),new HttpEntity<>(Message.builder().id(UUID.fromString(id)).build().toRequestString(),httpHeaders), MessageResponse.class);
return new ResponseEntity<>(entity.getBody().getPayload().get(0),entity.getStatusCode());
}
catch (Exception e) {
log.trace("ERROR",e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Testing
For testing, I wanted to make it usable on additional microservices regardless of them being part of the same project so I made it a completely independent module that was a EurekaClient of its own. This required two main utility functions.The first is is a random object generator and the second the same getUri from above that queries Eureka to get the server for the URI.
The random object generator is built using podam to populate POJOs with random data and then saves it through the relevant DAO microservice. This looks something like:
private User random() {
User user = podamFactory.manufacturePojoWithFullData(User.class);
restTemplate.postForEntity(getUri("user-dao","/save"),new HttpEntity<>(user.toRequestString(),httpHeaders),UserResponse.class);
return user;
}
Now that the POJO is created and saved, a basic service test would look something like:
@Test
public void testGet() {
User user = random();
ResponseEntity<UserResponse> entity = restTemplate.postForEntity(getUri("user-dao","/get"),new HttpEntity<>(user.toRequestString(),httpHeaders), UserResponse.class);
assert(entity.getStatusCode().is2xxSuccessful());
assertEquals(user.getUsername(), entity.getBody().getPayload().get(0).getUsername());
}
User Interface
The User Interface is extremely bare bones but serves as a starting point for testing. My goal would be to write this in a more advanced format as an exploration of a UI technology. For now it is simply SpringMVC using Freemarker templates.
Login
Login was done insecurely and at the most simple way for the sake of demonstration. In order to login as a user there is a /login/{username} api which is linked to from /UserList. /UserList provides a list of all known users including message and follow counts for you to select from. The logged in username is stored in plain text in a cookie.
Timeline
The timeline is the main part of the UI as it is the primary interface for the application. A user may access their own timeline from /timeline or another user’s timeline with /timeline/{username}. Either method calls /timeline-service/get/{username} to retrieve the relevant messages. This API in turn calls /timeline-servie/refresh/{username} which compiles a list of all messages for the user and all followed users which have not yet been stored in the timeline for that user. It then inserts them and returns the results.
The difference between the two timeline interfaces is that if accessing one’s own timeline they see an option to post a message while accessing another user’s timeline provides a follow or unfollow link as appropriate.
Updated Architecture
A few adjustments to the architecture were needed as the project was fleshed out. In addition the SearchService and NotificationService have not yet been developed.
Next Steps
In the next post of this series, I plan to break down how the load generation tool functions to similar customer traffic. I’ll come back to the SearchService and NotificationService as these are going to require additional technologies. Stay tuned!