Composite types created by combining the basic types like int, float etcetra.
Arrays
An array is a fixed length sequence of zero or more elements of particular type
Because of the fixed length constraints, Arrays are rarely used in Go
if “…” appears in place of length, that means the length of array is determined by number of initialisers
Size of array is part of it’s type, so [7] int is different from 9[int]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Output:
Element at index is 2
Length of array is 3
Index is 0, and value is 1
Index is 1, and value is 2
Index is 2, and value is 3
Length of array is 3
Slices
Slices are variable length sequences of elements the same type. A slice type is []T where T is the type of element.
A slice is a dynamically-sized flexible view into the elements of an array
A slice has three components: A pointer, length and a capacity
Unlike Arrays, slices are not comparable. We can not use “==” operator to test whether both slices have same elements or not
The built-in append function append s items to slices
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Map is an unordered collection of key and value pair
Keys are distinct
Update, insert, delete operations are in constant time
Key must be comparable using “==”
Maps can not be compared with each other
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Output:
map[0:Harsh 1:Yash]
map[0:Harsh]
Roll number of Harsh is 0
Roll number of Jain is 1
Struct
A struct is an aggregate data type that groups together zero or more named values of arbitrary types as a single entity. Like student data containing it’s id, name, class etc.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
If all the fields of struct are comparable, struct is comparable
JSON
JavaScript Object Notation (JSON) is a standard notation for sending and receiving structured information
Converting from Go data structure to JSON is called marshaling
Converting from JSON to Go data structure is called unmarhsaling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
There are four different size of signed integer type; 8, 16, 32, 64 bits, represented by int8, int16, int32 and int64
Similarly there are four different size of unsigned integer types; 8, 16, 32, 64 bits, represented by uint8, uint16, unit32 and uint64
There are also two types called int and unit which is the most efficient size for signed and unsigned integer on a particular platform
int is the mostly used type
There should not be any assumption about size of int as different compilers make different choice even on identical hardwares
rune is a synonym for int32 and is mostly used for a unicode value
byte is synonym for uint8
uintptr – Unsigned int which is used to hold all bits of a pointer value, length is unspecified
Explicit conversion needed for transferring value from one type to another i.e. to say, int32, int64 and int are three different values
Signed integer is in 2’s complement form
Remainder operator (%) is used only for int
The sign of remainder is always the sign of dividend, so (-7%5) and (-7%-5) both values to -2
After an arithmetic operation, if the result size is more than what we can represent in the result type, it is said to overflow. The higher order bits are simply discarded
Floating Point Numbers
Go provides two size of floating point numbers, float32 and float64
float64 should be preferred for most purpose as in case of float32, error accumulates rapidly
Digits may be omitted before or after decimal value, .98 and 3. are both legal declaration
Scientific notation using e is supported as well and is used in case of very large or very small number
const Avogadro = 6.02214129e23
Floating points are printed using %g verb
Complex Numbers
Go provides two size of complex numbers, complex64 and complex128 whose components are float32 and float64
The built-in functions create a complex number from its real and imaginary components
The built-in real and imag functions extract these components
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Boolean values can be combined with AND and OR operator
Strings
A string is an immutable sequence of bytes
Text strings are conventionally interpreted as UTF-8 encoded sequences
The built-in len function returns the number of bytes in a string
Index operation s[i] retrieves the byte at i-th index of string s
Attempting to access a byte outside this range results in a panic
The substring operation s[i:j] yields a new string consisting of the bytes of the original string starting at index i and continuing up to, but not including, the byte at index j.
The i-th byte is not necessarily i-th character as URL encoding of non ASCII requires two or more bytes
Strings may be compared with comparison operators like == and < and this comparison is done byte by byte
Constants
Constants are the expression whose value is known to the compiler
Evaluation of Constant is done at the compile time and not run time
const pi = 3.14159
We may omit the right-hand side expression for all but the first of the group, implying that the previous expression and its type should be used again in case of sequence of constants
const (
a=52
b
c= 27
d
)
fmt.Println(a, b, c, d)
Output :: "52 52 27 27"
The constant generator iota may be used to create a sequence of related values without spelling out each one explicitly. This is also known as enums
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
This declares Sunday to be 0, Monday to be 1, and so on
The name of go functions, variables, constants, types and packages abide by following rules:
Name begins with a letter or underscore
Name can have any number of additional letters, digits as well as underscores
Case in name matters; list and List are two different names
Go has 25 reserved words like if, map, package, go etcetera
If name begins with uppercase, it can be used outside it’s own package; example Printf of fmt package
Package names are in lower case
Names go bigger and have more explainable name as their scope increases, for example, name i is perfectly fine for a small loop
Name follows camel case notation
Declaration
Go program is stored in one or more files that ends with .go extension
Each file begin with a package name that says which package this file is part of
Package declaration is followed by the import declaration
import declaration is followed by package level declaration of constants, types, variables and functions in any order
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Here, boilingF is package level entity, i.e. visible through out the package.
Output of the code is:
boiling point in Centigrade 100 and in Fahrenheit 212
Variables
var name type = expression
var name = expression
var name type
All three declarations are legal. If the type is omitted as in the second initialisation, type is inferred from the expression. If the expression is not given as in the third case, variable is initialised with zero value which is “” for strings, 0 for numbers and false for booleans and nil for others.
There is no uninitialised variable in Go
var i, j, k int – Initialises three variable of type integer
var f, err = os.Open(name) – Method Open returns and initialises file and error
Short Variable Declaration
t := 0.0
In the expression above, we have declared and initialised a variable called t with 0.0.
Takes the form of variableName := initialisation
Used for the majority of the local variable
If the variable is already declared in that lexical block, short variable declaration acts as assignment
Pointers
A variable stores some value. A pointer has address of the variable.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
We have initialised a variable called f whose value is 100. Now, we have a variable called p which stores the address of f. &f basically gives the address of the f. We can access the value stored in that address by *p.
The output of the program above is:
value of f is 100 and the address where f is stored c000016040
value of f is 200 and the address where f is stored c000016040
The new Function
p := new(int)
New Operator is another way of declaring the variable.
Type Declarations
There are variables that share the same representation but they vary in the significance. And int may be loop index or timestamp or goal scored.
type Score int
In this example, we have made a type called Score where the underlying type is int.
Type Declaration occurs at package level
Conversion from one type to the other is allowed if the underlying type is same
Two values of different named type can not be compared directly
Package And Files
Packages in Go are for the same reason as libraries or modules in other languages. They provide a way for encapsulation, modularity as well as reuse.
In Go, exported identifiers start with an upper-case letter. Export is same as public method/variable/enum in Java or c++
It is an error to import a package and then not use it
It is suggested to use goImports tool which takes care of insertion and removal of imports
Package Initialisation
Package level variables are initialised in the order they are declared except for the dependencies which takes precedence
In case of multiple .go files, compiler is given .go file in the sorted order by name
There are init methods that can not be called or referenced
init functions are automatically executed when the program starts in the order in which they are declared in the file
one package is initialised at a time in order of the imports in the program. So, a package p that imports q can be initialised knowing fully well that q is initialised
main package is last to be initialised
Scope
The Scope of a declaration is the part of the code where a use of declared name refers to that declaration.
Scope of declaration is the region of code
Scope is compile time property
Lifetime of a variable is duration of time when it is referenced by other part of the program
Here, our code will wait until findCustomerDetails() works and return the result or the timeout happens. There is no way, to explicitly complete this future.
Let’s assume, we have to design a booking flow for parking. The flow looks like as follow:
Booking Flow
Future does not provide any way for call back after the completion of getCustomerDetails and getParkingDetails, so that we can start booking operation just after.
We would ideally want to do these three operation chained together, and such chaining is not provided by Future APIs.
Similarly, Exception Handling has to be done outside the future APIs.
Completable Future At Work
We would like to approach completable future through examples.
In the example above, the runAsync API of completable future takes runnable, which is provided in lambda. The example above does not show any difference between CompletableFuture as well as Future. But we will get to it.
With this introduction and motivation, it’s time to formally define Completable Future.
Definition
Completable Future implements Future. In a way, Completable Future is a future that can be explicitly completed. It can also act as an CompletionStage by providing way to support dependent functions. It also provides way to callback or in other words, support actions that can be triggered on it’s completion.
Salient Features
When multiple threads wanted to complete, cancel or completeExceptionally , only one of them succeeds
Actions supplied for dependent completions of non-async methods may be performed by the thread used by current completable future or any other caller of completion method
Async methods without having an explicit Executor argument are performed using the ForkJoinPool.commonPool()
In case of exceptional completion with a CompletionException, methods get() and get(long, TimeUnit) throw an ExecutionException
Usage
No Arguement
CompletableFuture<String> completableFuture = new CompletableFuture<>();
String value = completableFuture.get();
The above statement will cause program to run forever. Because there is nothing for the completable future to do.
This has already been explained above. In the example above, the runAsync API of completable future takes runnable, which is provided in lambda.
SupplyAsync
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Supply Async takes a supplier. Reminder: Supplier is a kind of functional interface that does not take any input but provides an output.
Here, we are giving Completable future a supplier lambda and returning a result which we then get as we would have done in Future.
The output of this program is as follow:
Task Running inside completable Future
I am the returned result
ThenAccept
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Let’s say you want to do some action after the completion of a Completable Future. There is nothing to be returned in that action, then you would use thenAccept as shown in the example above. ThenAccept takes input from the previous Completable Future and works on it.
The output of this program is as follow:
Task Running inside completable Future
I am the returned result
ThenRun
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Suppose you want to trigger some action after the completion of a completable future. And you don’t have any input from the completable feature as well as no output to be returned.
Output would be:
Task Running inside completable Future
I don't get any input from the previous completableFuture. I am just triggered after it.
ThenCombine
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ThenCombine allows to trigger a task after both the CompletableFuture is completed. Here, for example, we took 500ms to complete completableFutureFirst and after that, as soon as completableFutureSecond is completed, completableFutureThird is triggered.
Output is:
Task Running inside completable Future
Task Running inside completable Future
Time taken in the Completable Future operation is 563
Result is :: one plus two is three
Complete
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
For whatsoever reason, let’s say we have to complete the future, then we need to use completeAPI. Complete API will complete the future with whatever value we give in the arguement.
Output in this case would be:
Value After Complete: Completing the completable future with this default text
CompleteExceptionally
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Assume something wrong happened with the task in CompletableFuture and you want to complete it and throw and exception so that the client of your API does appropriate handling of the exception. In such cases, you would use completeExceptionally.
Output for this is as follow:
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException: Task in CompletableFuture Failed
at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
at com.company.CompletableFutureAPIUsage.demoCompleteExceptionally(CompletableFutureAPIUsage.java:38)
at com.company.CompletableFutureAPIUsage.main(CompletableFutureAPIUsage.java:20)
Caused by: java.lang.RuntimeException: Task in CompletableFuture Failed
at com.company.CompletableFutureAPIUsage.demoCompleteExceptionally(CompletableFutureAPIUsage.java:36)
… 1 more
Cancel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
You can cancel the future if you don’t need it anymore. Further, there is an option to cancel it if it’s running. CompletableFuture.get() throws an exception when you get in a cancelled completable future. isCancelled() API lets you check if it is cancelled or not.
Output is:
Task Running inside completable Future
Completable Future is cancelled :: true
Exception in thread "main" java.util.concurrent.CancellationException
at java.util.concurrent.CompletableFuture.cancel(CompletableFuture.java:2263)
at com.company.CompletableFutureAPIUsage.demoCancel(CompletableFutureAPIUsage.java:37)
at com.company.CompletableFutureAPIUsage.main(CompletableFutureAPIUsage.java:22)
Comparison of Iterative, Future as well as CompletableFuture
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
If you don’t know about GraphQL, this link is a good place to start. This article would assume that reader has basic knowledge of REST as well as Java.
Mutation
Drawing comparison from REST, any verb (e.g. GET, POST, PUT etc) are able to do server changes. But as convention, we use POST to make new resource and PUT for updating resource. In an almost similar fashion, any query in GraphQL can be used for server side changes, however, we set up a convention that any server side write should be done via mutations.
Extending the example in last post, we will try to write a mutation that creates a driver(i.e. stores the firstName as well as last name) of a given car Id.
The mutation field returns an object type which in the example shown above is Driver. As in cases of query, we can ask for nested object. This is meant to ask for updated state after the mutation.
Data Provider
We have established in our last post that every field in GraphQL has a data provider associated with it. The one for this simple mutation is not different. We have added a new type for the added mutation as shown below.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The data fetcher for the mutation field is shown in gist below. Here, we have used a in-memory data storage but this can connect to any database or call some other API as well.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The first screen only wanted car number on the basis of car id. Why are we sending driverId too. Perhaps this example is too simple to make worth of the extra unnecessary information that we are sending. But imagine the actual production database table of car; it will have colour, data of manufacture, kilometres travelled, chassis number, fuel type and the list goes on.
That’s not the biggest problem on earth. We can any day do something like below to get rid of unwanted information.
But that would mean writing as many API signature as number of use cases and more maintenance headache for backend engineers . Also, that means our API is non intuitive. This also increases the stress on documentation. Add to it, more coordination needed between backend and frontend developers.
With this premise, I would like to introduce GraphQL.
GraphQL
Though not exactly, we can take the liberty of saying, GraphQL is to be used instead of REST architecture.
GraphQL is:
query language for the API
specification
collection of tools
designed to operate over single endpoint over HTTP
optimised for performance
optimised for flexibility
server-side runtime for executing queries by using a type system we define for our data
isn’t tied to any specific database or storage engine
is backed by our existing code and data
For the sake of the tutorial, we will try to show graphQL running for the same mock ups and introduce some concepts on the way. You may download the sample demo project from here and run it as normal java project.
Endpoint
GraphQL is designed to operate on single endpoint over HTTP. Our API is as below:
Open the graphQL playground and enter the above mentioned link in the URL; select the URL Endpoint above the text box and click open.
Query
For the first screen in the mock up, we need car number based on the car Id. A simple query like the one shown below will be sufficient for the first screen. At this point, do not worry about how did that string “cabById” came into picture. And also do not worry about weird JSON like syntax. We will learn more about it as we go forward.
{
cabById(id: "car1") {
id
carNumber
}
}
Image showing running the query above
But what if, we want to also have driverId in the response. Simple, just add driver object and ask for id in the query.
{
cabById(id: "car1") {
id
carNumber
driver{
id
}
}
}
Query with driver id
The point which I am trying to make here is that the endpoint did not change. GraphQL is client driven. It enables clients to ask for exactly what they need and nothing more. It makes it easier to evolve APIs over time.
Now, that we know how does a query runs, let’s see the structure of the query. GraphQL asks for specific fields on objects. Here, in our first query, we are asking for id and car number on the car object that is queried by using it’s id – “car1”. The entity “carNumber” and “id” is known as fields in GraphQL language. The noticeable feature of query and response is that query and result have exactly the same shape. And this is what GraphQL guarantees; you always get back what you expect. The string (“id: “car1”) is called argument in GraphQL language.
Implement a GraphQL Server in Java
If you have stayed with me till now, hopefully you will be answer to the following introductory questions.
What is GraphQL?
How it is different from REST?
How the client uses GraphQL?
What is the basic structure of GraphQL?
The next part we will focus on building a GraphQL server. The same one from github which I was using it as example so far and hopefully you must have run it.
We are going to use spring. So, make a java maven project and use the following pom.xml.
Schema
Create a new file schema.graphqls in src/main/resources with the following content:
type Query {
cabById(id: ID): Cab
}
type Cab {
id: ID
carNumber: String
driver: Driver
}
type Driver {
id: ID
firstName: String
lastName: String
}
It has a top level field called cabById and other fields are self explanatory.
pom.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Please use your own group Id, artifact id or anyother project specific details.
Main File
The class with main method looks like the one in gist below. This is a simple Spring Boot Application main method.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
A Data Fetcher fetches the data for one field while the query is executed. When GraphQL Java is executing a query, it calls appropriate Data Fetcher for each field it encounters in the query. In another words, Every field from the schema has a DataFetcher. If there is not any defined data fetcher for any specific field, then the default PropertyDataFetcher is used. PropertyDataFetcher is the name of field in case of java class or key in case of Map. DataFetcher interface is defined as follow in the GraphQL java code.
Data Fetcher class for our project is shown in the gist below.
public interface DataFetcher<T> {
T get(DataFetchingEnvironment dataFetchingEnvironment) throws Exception;
}
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Our first method getCabByIdDataFetcher returns a DataFetcher implementation which takes a DataFetcherEnvironment and returns a car. We need to get the id argument from the cabById field in the query. We find the car that has the same id as asked in the query and return it.
Our second method getDriverDataFetcher is similar to the first one except for the fact that the carInStore object from first method is made available to it using the getSource method.
Glue in Data Fetcher and Schema
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This GraphQLProvider class has a “init” method that brings everything together and serves as GraphQL server. It parses the schema and attaches appropriate DataFetcher to each field. We will come to this classes again in subsequent post to discuss at length.
Run the code as you would normally run a java code.
At this point your graphQL server would be running. While this post tries to give a brief introduction about graphQL in general and how to use it, this is no way comprehensive and I hope to cover more later.
As always, feel free to drop in suggestion and comments.
We will use Java as programming language. This project will try to cover the following requirements. We will try to first define entities and then services. We will also build class diagram as we proceed as I don’t want to bombard with a picture fill of random boxes and arrows at the end.
List of booking Ids that this consumer has completed
Driver Entity
Driver entity should have following fields:
Name
Phone Number
Country Code
Possibility of abstract class Personal Info
If we notice following fields are common in both the entities:
Name
Phone Number
Country Code
That mean we can safely abstract out a PersonalInfo class which will be extended by both Driver and Rider class. Rider class can have “List of booking Ids that this consumer has completed”
At this point, our class diagram should look like one below:
Rider and Driver Entities
Vehicle Entity
Vehicle entity should have following fields:
Car Number
Latitude
Longitude
Type – To store type of car, for example: SUV, Compact, Sedan etcetera
IsAvailable – To store if the car is available to hire or not.
DriverId – We will not tightly couple driver and the vehicle. So, whenever a driver will login, he will pick up one of the vehicle. This field will store the id of driver who is currently driving this vehicle
Adding Vehicle Entity
Booking Entity
Booking entity should have following fields:
Booking Id
Rider User Id
Car Number
Start Time
End Time
Status
Storage Service
The requirement says that we need to use in-memory database. But what if tomorrow, someone asks us to implement the storage in database. Or in cache, something like Redis or memcache.
We will make an IStorageService. This will be an interface which will currently have just one implementation that stores data in-memory using Maps and Lists.
This interface will be like the one shown in gist below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Our new class diagram should look like the one below:
Storage Included
Rider Service, Driver Service, Vehicle Service and Booking Service
The first three services will be again have interfaces to save the data of new rider, driver and vehicle. In addition to that, vehicle service should also have contract to find the vehicle near the given position within the given distance as well as a contract to update the position of vehicle.
These three different services will be injected with Storage Service at initialisation so as to store the data.
Booking Service will have Vehicle Service injected in addition to the Storage Service as during booking we need to find vehicles within the given radius.
If you don’t understand this statement right now, its okay. Please wait for the main class at the end. Hope it will be clear then.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Problem Statement: Make a console based java application which support CRUD (Create, Read, Update, Delete) operations for e-commerce domain.
Application should be able to support following features:
Any user should be able to sign up, log in and log out.
Admin should be able to add, update and delete products.
Logged in user should be able to browse products.
Logged in user should have a shopping cart where user should be able to add multiple products.
User should have ability to checkout and total payable should be displayed while checkout
User should have following attributes: name, user id, address, date of birth
Product should have following attributes : name, product id, description and price
User and Product information should be persisted in database
Console should have option for all the operation mentioned above
What will you learn from this?
Designingclasses to store the user and Product information
Taking input from console and storing into models
Persisting data in database
Database table design
Coding best practices like naming of variables, class names, designing helping and service classes
Note: Beginners may get stuck in database connection. It’s advisable to complete this assignment using in memory data structures like list, map, set instead of database in the first phase. As a second stage, you can attempt to do the same assignment using database.
The question that frequently pops up during discussion with colleagues and friends is:
“What’s next?”
The question triggers a debate with very subjective answers which are evolving over time. Three years before, the answers included MS, MBA and startup. I used to work for a unicorn and I decided to join a startup to:
Learn about building a company
Apply my knowledge and skills that I had acquired
Explore the limit of my abilities
As the excitement of the new year sets in, this article is more like a note to self about how I perceive engineering as an organisation.
A random snippet
Engineering as an “org”
Like a company has a mission and vision, which guides the company’s action and strategy; similarly engineering as an organisation within the company should have a vision. It goes unsaid that one of the most important expectations from the engineering org is business continuity.
The term business continuity can be broken down into the following major components:
Uptime should be 100%
New features are incorporated
Bugs are solved
Latencies are within reasonable limits
Now, engineering org should have a mission and vision that could be derived from something that threatens the business continuity. For example, if the problem is attrition, which might lead to decrease in count of new features incorporated. Then the mission could be to hire and mentor talents. Similarly, if there are high count of bugs, the mission could be to do exhaustive testing with a long term vision of having more than 95 percent unit test code coverage.
Process
Any established company has set processes for the business continuity. Startups on the other hand are limited by the resources that they have. There is always a shortage of manpower. Setting up new processes with a startup can also be perceived as a nuisance as it might lead to unnecessary bureaucracy. This is detrimental for a startup environment where turnaround time is worshipped metric by non-engineering stakeholders.
In this scenario, the answer to the following question is something to be thought upon:
“How would you set up processes while taking buy-in from every stakeholder?”
One possible way could be proper communication of metric that stakeholders hold dear to them which is improved by setting up process. For example, engineering org decides to have a dedicated engineer to solve production bugs which results in reduction of turnaround time reduction from four days to one day. That’s 75% improvement and should be communicated to operation team about the advantage of having a dedicated on-call.
Refactoring
Refactoring is an exercise that has to be taken up periodically. New features frequently modifies existing flows to accommodate feature requirements. In most cases, unit tests take backseat as startups are always fighting for survival. With “time to market” as one of the important metrics to measure efficiency of engineering org, code is more prone to bugs and break some other existing feature silently. As such, there is a constant need to refactor small parts to control the chaos. Again, convincing all the stakeholders about refactoring could be a tough sale, provided refactoring does not directly lead to any change in measurable metrics. One observation is badly designed or coded module almost always result in regression bug. One way to attribute advantage of regression could be the reduction in the count of regression bugs.
Documentation
Documentation is a key area which is always neglected, both in terms of tech and product. We tried addressing this issue by rigorous tech grooming session where each of the features being developed is presented before the engineering team. The presented tech document usually contains the architecture diagram, class diagram and related notes. Such initiatives have helped in more mature designs because of the involvement of the whole team in developing design. Audience is someone who has worked on the module before, or someone who has solved bug related to the module or someone who makes an intelligent remark which is easy to be missed.
Such open discussions also mature to sharing knowledge about the tech concepts, both trivial and the advanced topic. Another result of discussion is possible points for roadmap when someone in the room asks:
“Isn’t this use case for Golang?”
“Shouldn’t we use some other design pattern?”
Proper documentation in terms of product features is always challenging. Product features are usually code with lot of configurability at different levels and stored in different data stores. With time, many less used flags/features are forgotten and there are always new features which conflict with existing feature.
Mentorship
Mentorship is all about enabling people. Mentorship is not about providing the fast paced solution rather, it is about enabling them to face problems. First step to mentor someone is to make them trust their abilities and making them sync with the feeling that “they belong there”. Subsequently, It’s important to let them challenge themselves by finding solutions on their own. There should be an honest debate about the solution in order to further improve it. There should be necessary freedom as well as close monitoring in order to provide them with nudges to improve.
Did you hear,”Filtering a list to get unique? Why not use set instead?”
Another interesting observation is to ask, “What is a good code?” The keywords in answers will be “fast”, “maintainable”, “extendable”, “modular” and “reusable”. A good way for someone to improve is to make them evaluate their own code with respect to the definition of “good code”. Chances are code is going in the right direction.
Another important aspect of mentoring is to make people aware of the environment. Someone develops a feature. How about asking them following questions:
“What’s the response time?”
“Could you use cache to improve response time?”
“But does using cache increase hardware cost?”
That sort of sums up the 2019 experiences. There are things which could have been better. But I guess it’s okay to make mistakes and learn from them.
For Injecting dependency using guice, you should first know what is Dependency Injection. Please go through this article to understand Dependency Injection.
Introduction
Now that we know what is Dependency Injection, we can use it in our code. With dependency injection, objects accept dependencies in their constructors. To construct an object, you need first to build its dependencies. But to build each dependency, you need its dependencies, and so on. So when you build an object, you really need to build an object graph.
Here guice comes into the picture. It can build the object graph for you and all you have to is use those dependencies in your code and need not worry about how and where to get these dependencies.
Guice in Action
Guice controls Dependency Injection by Guice bindings.These bindings are used by guice to map object types to their actual implementations. All the bindings are defined in a module.
Let’s create a small application to better understand this. It is a simple application which tells user which vehicle being used for test drive.
Interface for vehicle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Notice Vehicle class is bound to Bike implementation in module.
Now lets create class VehicleShopping with vehicle as dependency.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
We need to create a guice injector for this dependency injection to work. An injector builds the graphs of objects that make up your application and tracks the dependencies for each type and uses bindings to inject them. Thus, the first step is to create an injector and then use the injector to get the objects.This method is to be called once during application startup.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Here VehicleShopping class does not care which implementation is provided and all responsibility to get desired objects and their dependency resolved is handled by Guice.