Push Notification for Order Placed Using Observable Design Pattern with Spring Boot, WebSocket, Angular

Trang Nguyen
Geek Culture
Published in
4 min readSep 3, 2021

Honestly, I was about to give up doing this feature because a senior Java developer told me that the “Observable pattern” mainly works with mobile and embedded applications, not with the web, web environment is stateless. But after finishing the chat features, which gave me some confidence, I gave it another try on Observable pattern + WebSocket, and luckily it worked..

This feature is in my stock trading project which notifies traders when another trader has just purchased one stock.

I was thinking of two approaches to do this feature:

  • Short polling: Create a set of newly purchased stocks and store it in the backend. The front end will get this updated set by sending requests to the backend every 05 minutes using setInterval().
  • Observable pattern + WebSocket: Use Observable pattern to observe any new orders and use Websocket to automatically send notifications from server to client without calling HTTP requests.

Short polling: This isn’t an efficient approach. There are a few obvious downsides to this: It consumes server resources with a barrage of requests, and most requests will return empty if the data isn’t frequently updated.

Observable pattern + WebSocket: This is what I’m looking for, and I will explain it in this post.

First of all, I will create a PropertyChangedEventArgs<T>:

@AllArgsConstructor
public class PropertyChangedEventArgs<T>{
public T source; (1)
public String userId; (2)
public String symbol; (3)
}

(1): a reference to the source
(2), (3): the information that is being changed

This contains some information about changes to a particular property, and it is on the object of type T, and in my case T is a controller (OrderControllerImpl). The reason why I use a generic is that I want to keep it as flexible as possible.

Then I need to create an Observer interface

public interface Observer<T> {
void handle (PropertyChangedEventArgs<T> args);
}

“Observer” is an interface that is expected to be implemented by anyone that is interested in observing an object of type T. In my case, it is the OrderWSController that implements Observer interface to watch changes (a new order is created) in OrderControllerImpl.

Why is OrderWSController an observer but not another class? It’s because OrderWSController is responsible for sending data (notifications) from server to client using WebSocket instead of HTTP request.

Here’s how I implement Observer interface:

@Controller
public class OrderWSController implements Observer<OrderControllerImpl> {
private final SimpMessagingTemplate simpMessagingTemplate;

@Autowired
DailyService dailyService;

@Autowired
OrderService orderService;

OrderControllerImpl orderController;


public OrderWSController(SimpMessagingTemplate simpMessagingTemplate, OrderControllerImpl orderController){
this.simpMessagingTemplate = simpMessagingTemplate;
this.orderController = orderController;
this.orderController.subscribe(this); (1)
};

@Override (2)
public void handle(PropertyChangedEventArgs<OrderControllerImpl> args) {
String message = args.userId + " has just purchased " + args.symbol;
this.simpMessagingTemplate.convertAndSend("/queue/new-order", message);
}
}

(2) Whenever someone actually performs some changes (create an order/ buy a stock), it will get the changes information from PropertyChangedEventArgs (userId, symbol name) and then immediately send it to the clients that are subscribing to “/queue/new-order” endpoints.

(1) OrderWSController injects OrderControllerImpl to its class and let OrderControllerImpl add OrderWSController as one of its observers using subscribe(). The following classes will explain this idea in detail.

Next, I’m going to create an Observable class

public class Observable<T>{

(1) public List<Observer<T>> observers = new ArrayList<>();

(2) public void subscribe(Observer<T> observer) {
this.observers.add(observer);
}

(3) protected void notifyObservers(T source, String userId, String symbol) {
for (Observer<T> o : observers) {
o.handle(new PropertyChangedEventArgs<T>(source, userId, symbol));
}
}
}

(1) a list of all observers of type T that are watching the changes. In this case, it is the OrderWSController.
(2) add a new observer to a list of current observers. I also explain earlier how to add OrderWSController to a list of current observer
(3) notify all of the observers about changes information.

And here’s how to use this Observable class in the OrderControllerImpl:

@RestController
@RequestMapping("/order")
public class OrderControllerImpl extends Observable<OrderControllerImpl> implements OrderController {
@Autowired
private OrderService orderService;

@Autowired
private ResponseFactory responseFactory;

@PostMapping
public ResponseEntity<?> createNewOrder(@RequestBody OrderDto order) {
try{
Order saved = orderService.save(order);

if(saved.getOrderSide().name() =="BUY"){
notifyObservers(this, saved.getUser().getUserId(), saved.getSymbol().getSymbol());

}

return responseFactory.success(saved);
}
catch(IllegalArgumentException ex){
throw new BadRequestException(ex.getMessage());
}
catch(Exception ex){
throw new InternalServerException("Unable to create new order.");
}
}
}

Finally, in my Angular app, I need to subscribe to the endpoint and notify users when there’s a new order.

ngOnInit(): void {
this.initializeWebSocketConnection();
}

initializeWebSocketConnection(): any {
console.log('connected to ws ...');

const ws = new SockJS(this.serverUrl);

this.stompClient = Stomp.over(ws);

const that = this;

this.stompClient.connect({}, (frame) => {
that.stompClient.subscribe(`/queue/new-order`, (order) => {
let userId = order.body.split(" ")[0];
if (this.auth.readToken().userId != userId) {
this.snackBar.open(order.body, "", {
duration: 3000,
verticalPosition: "top",
horizontalPosition: "center"
});
}
});
}, (err) => {
console.log(err);
});
}

You can find my source code here.

However, I’m still not sure if this is an optimal solution. Feel free to leave me feedback and I’m all ears.

If you enjoy this article, please clap it up 👏 and share it so that others can find it! 😄.

--

--

Trang Nguyen
Geek Culture

Computer Programming Student @Seneca. Writing to share solutions and encourage my sister to write.