SOLID Principles in Spring Boot Starter Code
November 28, 2020
Photo from Unsplash byJukan Tateisi
This article will take you through the starter code if you’ve never worked with Spring before. By the end of this article you will have an understanding of what’s going on in the starter code and will be prepared to work through the two assignments as we build out our API for our personal digital wallet.
Cryptocurrency
If you’re unfamiliar with cryptocurrency, cryptocurrency pledges to be the future of finance relying on the underlying technology called a blockchain. Some of the design goals of cryptocurrency include: security, anonymity, immutability, ease of transaction, and peer-to-peer transactions that don’t need a third-party like a bank or a credit card to facilitate.
Some of the popular names you may have heard include Bitcoin and Ethereum. These are two of the most popular cryptocurrencies with Bitcoin being the original and most popular cryptocurrency and Ethereum building an open-source platform that allows for the development of Distributed Applications (Dapps).
To use blockchain technologies usually all you need is a digital wallet which holds all of your crytography keys and allows you to purchase, sell, and send cryptocurrency.
In this assignment, we will be working on creating a personal API for our own digital wallet that holds imaginary Bitcoin and Ethereumn information.
Starter Code
If you haven’t already please make sure you’ve worked through the Setup guide and were able to get the Spring application running in your local environment.
The basic functionality that we are implementing in our digital wallet API is the ability to:
- View the balance of our digital wallet
- Make transactions with our digital wallet
- Reset the balance of our digital wallet
In the starter code, there are two classes that are implemented which handle all of this functionality:
1.../java/solidSpring/2 DigitalWalletController.java3 DigitalWallet.java4 HelloController.java5 SolidSpringApplication.java6
The DigitalWalletController
class is a controller that handles the receiving and responding of web requests that reach any of the endpoints defined in the class.
1package solidSpring;23import org.springframework.http.HttpStatus;4import org.springframework.web.bind.annotation.GetMapping;5import org.springframework.web.bind.annotation.RequestParam;6import org.springframework.web.bind.annotation.RestController;7import org.springframework.web.server.ResponseStatusException;89import solidSpring.DigitalWallet;1011@RestController12public class DigitalWalletController {1314 @GetMapping("/balance")15 public DigitalWallet wallet() {16 return DigitalWallet17 .getInstance()18 .accountBalance();19 }2021 @GetMapping("/transaction")22 public DigitalWallet transaction(23 @RequestParam(value = "value", defaultValue = "0" )24 String value )25 {26 try27 {28 double parsedValue = Double.parseDouble( value );2930 try31 {32 return DigitalWallet33 .getInstance()34 .processTransaction( parsedValue );35 }36 catch ( Exception e )37 {38 throw new ResponseStatusException(39 HttpStatus.BAD_REQUEST, e.toString() );40 }4142 }43 catch ( NumberFormatException e )44 {45 throw new ResponseStatusException(46 HttpStatus.BAD_REQUEST, e.toString() );47 }48 }4950 @GetMapping("/zero")51 public DigitalWallet zero()52 {53 return DigitalWallet54 .getInstance()55 .zero();56 }57}58
In this DigitalWalletController
class, Spring uses the @RestController
annotation above the class definition to signifiy that the class
describes an endpoint that should be made available over the web.
Inside the class there are three methods defined with a @GetMapping(...)
annotation. This @GetMapping(...)
annotation let’s Spring know that whenever the route defined inside of the @GetMapping(...)
is reached via HTTP - GET, the method defined for the route will be invoked to process the HTTP request.
If you take a look at the transaction/
endpoint, you will notice it’s a little different than the other two. Inside the method signature you will see @RequestParam(value = "value", defaultValue = "0" ) String value
defined. @RequestParam
is another special annotation that is used to tell Spring that there is a HTTP parameter that is expected to be passed to the endpoint. Inside the annotation there are two paremeters, the first is the key of the parameter that is being passed as part of the HTTP - GET request (called “value” in this endpoint), and the second parameter is the default value in case an explicit parameter is not set as part of the request. Then outside of the annotation you can see String value
also defined as part of the method signature. Essentially what is happening here, Spring is parsing the HTTP - GET request and if a parameter specified as value
is passed in, it will serialize the request and store the value in the locally scoped parameter String value
which can be used inside the scope of the method.
For each of the endpoints defined inside of the DigitalWalletController
, you can see that they all have a return type of a class named DigitalWallet
. This return type is used to serialize a Java object into an HTTP response using a Java library called Jackson2 that is configured automatically in Spring Boot. This process of serialization is essentially the conversion of a Java object to JSON, or a format that clients (mobile/web) can recieve and process from our server.
Serialization
If you aren’t familiar with the process of serialization, it’s basically an awesome way to convert an object in Java to a stream of bytes that you can write to a file. This process is typically used in API development to read requests from a client and convert them into Java objects that we can run on the JVM. Another common use case is if we need to persist some data during some type of execution flow in the service, you can serialize a Java object and write the stream to a database that you can index and recall and convert back into a Java object using serialization. (This is super cool btw 🤑)
Digging into this DigitalWallet.java
class:
1package solidSpring;23public class DigitalWallet {45 private static DigitalWallet wallet = new DigitalWallet();67 private static final String NAME = "Bitcoin";8 private static final String WHITE_PAPER = "https://bitcoin.org/bitcoin.pdf";9 private double BTC = 0;1011 private DigitalWallet() { };1213 public static DigitalWallet getInstance()14 {15 return wallet;16 }171819 public DigitalWallet processTransaction( double amount ) throws Exception20 {21 if( BTC + amount < 0 )22 {23 throw new Exception(24 String.format("\nInsufficient funds:\n\t BTC" +25 " Available: %1$s\n\t BTC Requested: %2$s",26 BTC, amount));27 } else {28 BTC = BTC + amount;29 }3031 return this;32 }3334 public DigitalWallet zero()35 {36 BTC = 0;3738 return this;39 }4041 public DigitalWallet accountBalance()42 {43 return this;44 }4546 /////////////////////////////////////////47 // For Serialization48 ////////////////////////////////////////49 public String getName()50 {51 return NAME;52 }5354 public double getBTC()55 {56 return BTC;57 }5859 public String getWhitePaper()60 {61 return WHITE_PAPER;62 }6364 public double getSatoshis()65 {66 return BTC * 100000000;67 }6869}70
You can see that the class is making use of the Singleton pattern. DigitalWallet
also has three instance attributes outside of the Singleton instance: NAME
, WHITE_PAPER
, and BTC
. These attributes hold the name of Bitcoin, the link to the Bitcoin whitepaper, and the amount of Bitcoin in our digital wallet.
Inspecting the rest of the class, we can see the three methods that are invoked in DigitalWalletController
: processTransaction
, zero
, and accountBalance
. These classes handle transactions of Bitcoin in the digital wallet, set the amount of Bitcoin in our digital wallet to zero, and retrieve the balance of Bitcoin in the digital wallet respectively.
At the bottom of the class, you will see four methods that Spring uses to serialize the DigitalWallet
instance to JSON: getBTC
, getName
, getWhitePaper
, and getSatoshis
(satoshis are the small atomic units that make up a Bitcoin). Important: for a class that is serialized in Spring, any attribute “getter” (get*) is automatically used for serialization.
Now that we’ve run through all of the starter code, let start our server and play around with the API.
API Walkthrough
To test the API, start your server in Eclipse and navigate to http://localhost:8080
in the browser. You should see
1Greetings from Spring Boot!2
displayed in your browser.
Now let’s test the three endpoints defined in the API so far.
balance/
If you navigate to http://localhost:8080/balance
in the browser, it will fetch the starting balance of the digital wallet and you should see:
1{"name":"Bitcoin","btc":0.0,"whitePaper":"https://bitcoin.org/bitcoin.pdf","satoshis":0.0}2
in your browser. This is the serialized response of the balance/
endpoint defined in DigitalWalletController.java
. You can see that all of the “getter” methods specified in the DigitalWallet
class are serialized and returned as part of the endpoint’s response.
transaction/
Now, say we want to make a transaction and add some Bitcoin to our digital wallet. To do this, enter http://localhost:8080/transaction?value=1.22
into the browser. You will now see in the response that we added 1.22 Bitcoin to our digital wallet:
1{"name":"Bitcoin","btc":1.22,"whitePaper":"https://bitcoin.org/bitcoin.pdf","satoshis":1.22E8}2
The btc
field in the response now has a value of 1.22 instead of 0.0.
zero/
A quick helper endpoint was also added so you can easily reset your digital wallet while testing. If you enter http://localhost:8080/zero
into the browser, you will see in the response that the amount of Bitcoin in the digital wallet was set to 0.0:
1{"name":"Bitcoin","btc":0.0,"whitePaper":"https://bitcoin.org/bitcoin.pdf","satoshis":0.0}2
Error Handling
Some light-weight error handling was also built in to the starter code if you look in the processTransaction
method in DigitalWallet
if you
try to exchange more Bitcoin than what you have in your digital wallet, it will throw a 400 error (Bad Request) describing that you have insufficient funds to complete the transaction. To test this, you should now have zero BTC in your digital wallet (if not call http://localhost:8080/zero
to set the balance to zero), pass in a negative amount as the value to the transaction/
endpoint e.g http://localhost:8080/transaction?value=-1.22
. You should see an error page display with a stack trace as well a specific error message describing that there were insufficient funds to complete the transaction.
This wraps up the extent of the API implemented in the starter code, now you’re ready for the assignments 🚀
Written by Riley Miller, follow me on Twitter 🐦