SlideShare a Scribd company logo
Creating an Uber Clone - Part XXVI
Before we proceed into the actual driver app work & push notification we need to implement all the infrastructure in the server side. I also need to implement some
changes we need in the protocol.
private String hailingFrom;
private String hailingTo;
private String pushToken;
public RideDAO getRideDao() {
return new RideDAO(id, givenName, hailingFrom, hailingTo);
}
public UserDAO getDao() {
return new UserDAO(id, givenName, surname, phone, email,
facebookId, googleId, driver, car, currentRating,
latitude, longitude, direction, pushToken);
}
public UserDAO getPartialDao() {
return new UserDAO(id, givenName, surname, null, null,
null, null, driver, car, currentRating, latitude,
longitude, direction, pushToken);
}
User (Server)
Lets start by looking at the User class. I had to add 3 new fields and modify/add some methods.

hailingFrom & hailingTo allow us to communicate our trip details with the driver community
private String hailingFrom;
private String hailingTo;
private String pushToken;
public RideDAO getRideDao() {
return new RideDAO(id, givenName, hailingFrom, hailingTo);
}
public UserDAO getDao() {
return new UserDAO(id, givenName, surname, phone, email,
facebookId, googleId, driver, car, currentRating,
latitude, longitude, direction, pushToken);
}
public UserDAO getPartialDao() {
return new UserDAO(id, givenName, surname, null, null,
null, null, driver, car, currentRating, latitude,
longitude, direction, pushToken);
}
User (Server)
I need the pushToken of drivers so we can hail them directly from the app
private String hailingFrom;
private String hailingTo;
private String pushToken;
public RideDAO getRideDao() {
return new RideDAO(id, givenName, hailingFrom, hailingTo);
}
public UserDAO getDao() {
return new UserDAO(id, givenName, surname, phone, email,
facebookId, googleId, driver, car, currentRating,
latitude, longitude, direction, pushToken);
}
public UserDAO getPartialDao() {
return new UserDAO(id, givenName, surname, null, null,
null, null, driver, car, currentRating, latitude,
longitude, direction, pushToken);
}
User (Server)
I'll discuss the `RideDAO` class soon, it allows us to send details about the trip to drivers
private String hailingFrom;
private String hailingTo;
private String pushToken;
public RideDAO getRideDao() {
return new RideDAO(id, givenName, hailingFrom, hailingTo);
}
public UserDAO getDao() {
return new UserDAO(id, givenName, surname, phone, email,
facebookId, googleId, driver, car, currentRating,
latitude, longitude, direction, pushToken);
}
public UserDAO getPartialDao() {
return new UserDAO(id, givenName, surname, null, null,
null, null, driver, car, currentRating, latitude,
longitude, direction, pushToken);
}
User (Server)
Not much of a change but I added the pushToken to the UserDAO factory methods
public class RideDAO implements Serializable {
private long userId;
private String name;
private String from;
private String destination;
public RideDAO() {
}
public RideDAO(long userId, String name, String from, String destination) {
this.userId = userId;
this.name = name;
if(this.name == null) {
this.name = "[Unnamed User]";
}
this.from = from;
this.destination = destination;
}
// getters and setters trimmed out
}
RideDAO
The RideDAO class is a pretty trivial object. There's no point in enumerating the details of this class as it's pretty simple. The main usage for this is in the new RideService
which I will get to soon but first we need to discuss the Ride class.
@Entity
public class Ride {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
@ManyToOne
private User passenger;
@ManyToOne
private User driver;
@OneToMany
@OrderBy("time ASC")
private Set<Waypoint> route;
private BigDecimal cost;
private String currency;
private boolean finished;
private boolean started;
// trimmed out constructors, getters and setters
}
Ride
Ride isn't as simple as RideDAO despite their common name. It contains far more information. Currently we don't use all of that but the fact that it's logged will let you
provide all of that information within the app or a management app easily.

The Ride class is a JPA entity similar to the User class.

I used an auto-increment value for the id instead of a random string. I wanted to keep things simple but notice this can expose a security vulnerability of scanning for
rides…
@Entity
public class Ride {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
@ManyToOne
private User passenger;
@ManyToOne
private User driver;
@OneToMany
@OrderBy("time ASC")
private Set<Waypoint> route;
private BigDecimal cost;
private String currency;
private boolean finished;
private boolean started;
// trimmed out constructors, getters and setters
}
Ride
The passenger & driver are relational database references to the respective database objects representing each one of them
@Entity
public class Ride {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
@ManyToOne
private User passenger;
@ManyToOne
private User driver;
@OneToMany
@OrderBy("time ASC")
private Set<Waypoint> route;
private BigDecimal cost;
private String currency;
private boolean finished;
private boolean started;
// trimmed out constructors, getters and setters
}
Ride
The route itself is a set of waypoints sorted by the time associated with the given waypoint. We'll discuss waypoints soon enough but technically it's just a set of
coordinates
@Entity
public class Ride {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
@ManyToOne
private User passenger;
@ManyToOne
private User driver;
@OneToMany
@OrderBy("time ASC")
private Set<Waypoint> route;
private BigDecimal cost;
private String currency;
private boolean finished;
private boolean started;
// trimmed out constructors, getters and setters
}
Ride
I really oversimplified the cost field. It should work for sum and currency but it's usually not as simple as that. It's important to use something like BigDecimal and not
double when dealing with financial numbers as double is built for scientific usage and has rounding errors
@Entity
public class Ride {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
@ManyToOne
private User passenger;
@ManyToOne
private User driver;
@OneToMany
@OrderBy("time ASC")
private Set<Waypoint> route;
private BigDecimal cost;
private String currency;
private boolean finished;
private boolean started;
// trimmed out constructors, getters and setters
}
Ride
We have two boolean flags, a ride is started once a passenger is picked up. It’s finished once he is dropped off or if the ride was canceled
public interface RideRepository extends CrudRepository<Ride, Long> {
@Query("select b from Ride b where b.finished = false and b.driver.id = ?1")
public List<Ride> findByNotFinishedUser(long id);
}
RideRepository
The companion CRUD RideRepository is pretty standard with one big exception. I added a special case finder that lets us locate the User that is currently hailing a car.
Notice the syntax b.driver.id = ?1 which points through the relation to the driver object.
@Entity
public class Waypoint {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private long time;
private double latitude;
private double longitude;
private float direction;
// trimmed out constructors, getters and setters
}
Waypoint
The Waypoint entity referenced from the Ride entity is pretty trivial. Notice we still need a unique id for a waypoint even if we don't actually use it in code...

The interesting part here is the time value which is the value of System.currentTimeMillis(). This allows us to build a path based on the time sequence. It will also allow us
to reconstruct a trip and generate additional details such as speed/cost if we wish to do that in the future.

Notice that there is also a WaypointRepository interface. I’m skipping it as it contains no actual code
@Service
public class RideService {
@Autowired
private UserRepository users;
@Autowired
private RideRepository rides;
@Transactional
public UserDAO hailCar(String token, boolean h, String from, String to) {
User u = users.findByAuthToken(token).get(0);
if(h) {
if(u.getAssignedUser() != null) {
long driverId = u.getAssignedUser();
u.setAssignedUser(null);
users.save(u);
User driver = users.findOne(driverId);
return driver.getPartialDao();
}
} else {
u.setAssignedUser(null);
}
u.setHailing(h);
u.setHailingFrom(from);
u.setHailingTo(to);
users.save(u);
return null;
}
RideService
The RideService class serves the same purpose as the UserService class focusing on rides and driver related features. I could have just stuck all of this logic into one
huge class but separating functionality to different service classes based on logic makes sense.

We manipulate both the rides and users CRUD objects from this class
@Service
public class RideService {
@Autowired
private UserRepository users;
@Autowired
private RideRepository rides;
@Transactional
public UserDAO hailCar(String token, boolean h, String from, String to) {
User u = users.findByAuthToken(token).get(0);
if(h) {
if(u.getAssignedUser() != null) {
long driverId = u.getAssignedUser();
u.setAssignedUser(null);
users.save(u);
User driver = users.findOne(driverId);
return driver.getPartialDao();
}
} else {
u.setAssignedUser(null);
}
u.setHailing(h);
u.setHailingFrom(from);
u.setHailingTo(to);
users.save(u);
return null;
}
RideService
Hailing is a transactional method, this means that all operations within the method will either succeed or fail depending on the outcome. This is important to prevent an
inconsistent state in the database
@Service
public class RideService {
@Autowired
private UserRepository users;
@Autowired
private RideRepository rides;
@Transactional
public UserDAO hailCar(String token, boolean h, String from, String to) {
User u = users.findByAuthToken(token).get(0);
if(h) {
if(u.getAssignedUser() != null) {
long driverId = u.getAssignedUser();
u.setAssignedUser(null);
users.save(u);
User driver = users.findOne(driverId);
return driver.getPartialDao();
}
} else {
u.setAssignedUser(null);
}
u.setHailing(h);
u.setHailingFrom(from);
u.setHailingTo(to);
users.save(u);
return null;
}
RideService
This method can be invoked to start and stop hailing. In this case we use the assigned user property to detect if a driver accepted the ride. If so we return the driver data
to the client
users.save(u);
return null;
}
public RideDAO getRideData(long userId) {
User u = users.findOne(userId);
if(u == null) {
return null;
}
return u.getRideDao();
}
@Transactional
public long acceptRide(String token, long userId) {
User driver = users.findByAuthToken(token).get(0);
User passenger = users.findOne(userId);
if(!passenger.isHailing()) {
throw new RuntimeException("Not hailing");
}
passenger.setHailing(false);
passenger.setAssignedUser(driver.getId());
driver.setAssignedUser(userId);
users.save(driver);
users.save(passenger);
Ride r = new Ride();
r.setDriver(driver);
r.setPassenger(passenger);
rides.save(r);
RideService
When a driver gets a notification of a ride he invokes this method to get back the data about the ride
users.save(u);
return null;
}
public RideDAO getRideData(long userId) {
User u = users.findOne(userId);
if(u == null) {
return null;
}
return u.getRideDao();
}
@Transactional
public long acceptRide(String token, long userId) {
User driver = users.findByAuthToken(token).get(0);
User passenger = users.findOne(userId);
if(!passenger.isHailing()) {
throw new RuntimeException("Not hailing");
}
passenger.setHailing(false);
passenger.setAssignedUser(driver.getId());
driver.setAssignedUser(userId);
users.save(driver);
users.save(passenger);
Ride r = new Ride();
r.setDriver(driver);
r.setPassenger(passenger);
rides.save(r);
RideService
If the driver wishes to accept the ride he invokes this transactional method. The method accepts the token from the driver and the id of the user hailing the ride. It creates
a new Ride entity and returns its ID, from this point on we need to refer to the Ride id and not the user id or token
User driver = users.findByAuthToken(token).get(0);
User passenger = users.findOne(userId);
if(!passenger.isHailing()) {
throw new RuntimeException("Not hailing");
}
passenger.setHailing(false);
passenger.setAssignedUser(driver.getId());
driver.setAssignedUser(userId);
users.save(driver);
users.save(passenger);
Ride r = new Ride();
r.setDriver(driver);
r.setPassenger(passenger);
rides.save(r);
return r.getId();
}
public void startRide(long rideId) {
Ride current = rides.findOne(rideId);
current.setStarted(true);
rides.save(current);
}
public void finishRide(long rideId) {
Ride current = rides.findOne(rideId);
current.setFinished(true);
rides.save(current);
}
}
RideService
Start ride and finish ride are invoked by the driver when he picks up the passenger and when he drops him off. Normally, finish ride should also handle elements like
billing etc. but I won't go into that now
@Controller
@RequestMapping("/ride")
public class RideWebservice {
@Autowired
private RideService rides;
@RequestMapping(method=RequestMethod.GET,value = "/get")
public @ResponseBody RideDAO getRideData(long id) {
return rides.getRideData(id);
}
@RequestMapping(method=RequestMethod.GET,value="/accept")
public @ResponseBody String acceptRide(@RequestParam(name="token", required = true) String token,
@RequestParam(name="userId", required = true) long userId) {
long val = rides.acceptRide(token, userId);
return "" + val;
}
@RequestMapping(method=RequestMethod.POST,value="/start")
public @ResponseBody String startRide(@RequestParam(name="id", required = true) long rideId) {
rides.startRide(rideId);
return "OK";
}
@RequestMapping(method=RequestMethod.POST,value="/finish")
public @ResponseBody String finishRide(@RequestParam(name="id", required = true) long rideId) {
rides.finishRide(rideId);
return "OK";
}
}
RideWebservice
The next step is bridging this to the user through a webservice...

The RideWebservice class exposes the RideService calls almost verbatim to the client.

The get call fetches the RideDAO for the given user id
@Controller
@RequestMapping("/ride")
public class RideWebservice {
@Autowired
private RideService rides;
@RequestMapping(method=RequestMethod.GET,value = "/get")
public @ResponseBody RideDAO getRideData(long id) {
return rides.getRideData(id);
}
@RequestMapping(method=RequestMethod.GET,value="/accept")
public @ResponseBody String acceptRide(@RequestParam(name="token", required = true) String token,
@RequestParam(name="userId", required = true) long userId) {
long val = rides.acceptRide(token, userId);
return "" + val;
}
@RequestMapping(method=RequestMethod.POST,value="/start")
public @ResponseBody String startRide(@RequestParam(name="id", required = true) long rideId) {
rides.startRide(rideId);
return "OK";
}
@RequestMapping(method=RequestMethod.POST,value="/finish")
public @ResponseBody String finishRide(@RequestParam(name="id", required = true) long rideId) {
rides.finishRide(rideId);
return "OK";
}
}
RideWebservice
Start and finish rides are again very simple with only one argument which is the ride id
public void updatePushToken(String token, String pushToken) {
User u = users.findByAuthToken(token).get(0);
u.setPushToken(pushToken);
users.save(u);
}
UserService
We also have to add some minor changes to the UserService and LocationService classes. Lets start with the UserService class. Drivers need a push token so we can
hail them. This is always set outside of the user creation code for two reasons. 

The first time around the user is created but the push key isn't there yet (it arrives asynchronously)

Push is re-registered in every launch and refreshed, there is no reason to update the entire object for that
@RequestMapping(method = RequestMethod.GET,value = "/setPushToken")
public @ResponseBody String updatePushToken(
@RequestParam(name="token", required = true) String token,
@RequestParam(name="pushToken", required = true) String
pushToken) {
users.updatePushToken(token, pushToken);
return "OK";
}
UserWebservice
The UserWebservice class needs to mirror these changes obviously...

There isn't much here we just added a new setPushToken URL and we accept this update.
public class LocationService {
@Autowired
private UserRepository users;
@Autowired
private RideRepository rides;
@Autowired
private WaypointRepository waypoints;
public void updateUserLocation(String token,double lat,double lon,float dir) {
List<User> us = users.findByAuthToken(token);
User u = us.get(0);
u.setLatitude(lat);
u.setLongitude(lat);
u.setDirection(dir);
users.save(u);
if(u.isDriver() && u.getAssignedUser() != null) {
List<Ride> r = rides.findByNotFinishedUser(u.getId());
if(r != null && !r.isEmpty()) {
Ride ride = r.get(0);
if(ride.isStarted() && !ride.isFinished()) {
Set<Waypoint> route = ride.getRoute();
Waypoint newPosition = new Waypoint(
System.currentTimeMillis(), lat, lon, dir);
waypoints.save(newPosition);
route.add(newPosition);
ride.setRoute(route);
rides.save(ride);
LocationService
The LocationService needs a bit more work.

Every time we update a users location we check if he's a driver on a ride
public class LocationService {
@Autowired
private UserRepository users;
@Autowired
private RideRepository rides;
@Autowired
private WaypointRepository waypoints;
public void updateUserLocation(String token,double lat,double lon,float dir) {
List<User> us = users.findByAuthToken(token);
User u = us.get(0);
u.setLatitude(lat);
u.setLongitude(lat);
u.setDirection(dir);
users.save(u);
if(u.isDriver() && u.getAssignedUser() != null) {
List<Ride> r = rides.findByNotFinishedUser(u.getId());
if(r != null && !r.isEmpty()) {
Ride ride = r.get(0);
if(ride.isStarted() && !ride.isFinished()) {
Set<Waypoint> route = ride.getRoute();
Waypoint newPosition = new Waypoint(
System.currentTimeMillis(), lat, lon, dir);
waypoints.save(newPosition);
route.add(newPosition);
ride.setRoute(route);
rides.save(ride);
LocationService
Assuming we have a Ride object we check if this is currently an ongoing ride that wasn't finished
public class LocationService {
@Autowired
private UserRepository users;
@Autowired
private RideRepository rides;
@Autowired
private WaypointRepository waypoints;
public void updateUserLocation(String token,double lat,double lon,float dir) {
List<User> us = users.findByAuthToken(token);
User u = us.get(0);
u.setLatitude(lat);
u.setLongitude(lat);
u.setDirection(dir);
users.save(u);
if(u.isDriver() && u.getAssignedUser() != null) {
List<Ride> r = rides.findByNotFinishedUser(u.getId());
if(r != null && !r.isEmpty()) {
Ride ride = r.get(0);
if(ride.isStarted() && !ride.isFinished()) {
Set<Waypoint> route = ride.getRoute();
Waypoint newPosition = new Waypoint(
System.currentTimeMillis(), lat, lon, dir);
waypoints.save(newPosition);
route.add(newPosition);
ride.setRoute(route);
rides.save(ride);
LocationService
If so we add a waypoint to the ride and update it so we can later on inspect the path of the Ride. 

This pretty much tracks rides seamlessly. If we wanted to be really smart we could detect the driver and user position to detect them traveling together and automatically
handle the ride. There are obviously problems with this as it means a user can't order a cab for someone else but it might be an interesting feature since we have two
close data points…

More Related Content

PDF
Creating an Uber Clone - Part XXVI.pdf
PDF
Creating an Uber Clone - Part XI - Transcript.pdf
PDF
Creating an Uber Clone - Part XXIX - Transcript.pdf
PDF
Creating an Uber Clone - Part XI.pdf
PDF
Creating an Uber Clone - Part XXXI.pdf
PDF
Creating an Uber Clone - Part XXIX.pdf
PDF
Creating an Uber Clone - Part XXX - Transcript.pdf
PDF
Creating an Uber Clone - Part XXX.pdf
Creating an Uber Clone - Part XXVI.pdf
Creating an Uber Clone - Part XI - Transcript.pdf
Creating an Uber Clone - Part XXIX - Transcript.pdf
Creating an Uber Clone - Part XI.pdf
Creating an Uber Clone - Part XXXI.pdf
Creating an Uber Clone - Part XXIX.pdf
Creating an Uber Clone - Part XXX - Transcript.pdf
Creating an Uber Clone - Part XXX.pdf

Similar to Creating an Uber Clone - Part XXVI - Transcript.pdf (20)

PDF
Creating an Uber Clone - Part XII - Transcript.pdf
PDF
Creating an Uber Clone - Part XXVII - Transcript.pdf
DOCX
Spring Orielly
PDF
DDD-rail Your Monorail
PDF
Creating a Facebook Clone - Part XVII - Transcript.pdf
PDF
Creating an Uber Clone - Part XXXI - Transcript.pdf
PDF
---BUS-- public class Bus { String busIdentifier- String driverName- d.pdf
PPTX
FYP_Interim 30329^J 28912[updated)[1][1].pptx
PDF
BreizhCamp 2013 - Pimp my backend
PPTX
High Performance Cloud Native APIs Using Apache Geode
PDF
Broken windows de práticas ágeis
PDF
Spring 3: What's New
PDF
Google Cloud Endpoints: Building Third-Party APIs on Google AppEngine
PDF
Questioning the status quo
PPTX
Android dev toolbox
PPTX
Uber system design architecture for embedded system
PDF
React Native: Developing an app similar to Uber in JavaScript
PDF
Alexander Graebe
PPTX
Designing REST API automation tests in Kotlin
PDF
Creating an Uber Clone - Part XII.pdf
Creating an Uber Clone - Part XII - Transcript.pdf
Creating an Uber Clone - Part XXVII - Transcript.pdf
Spring Orielly
DDD-rail Your Monorail
Creating a Facebook Clone - Part XVII - Transcript.pdf
Creating an Uber Clone - Part XXXI - Transcript.pdf
---BUS-- public class Bus { String busIdentifier- String driverName- d.pdf
FYP_Interim 30329^J 28912[updated)[1][1].pptx
BreizhCamp 2013 - Pimp my backend
High Performance Cloud Native APIs Using Apache Geode
Broken windows de práticas ágeis
Spring 3: What's New
Google Cloud Endpoints: Building Third-Party APIs on Google AppEngine
Questioning the status quo
Android dev toolbox
Uber system design architecture for embedded system
React Native: Developing an app similar to Uber in JavaScript
Alexander Graebe
Designing REST API automation tests in Kotlin
Creating an Uber Clone - Part XII.pdf

More from ShaiAlmog1 (20)

PDF
The Duck Teaches Learn to debug from the masters. Local to production- kill ...
PDF
create-netflix-clone-06-client-ui.pdf
PDF
create-netflix-clone-01-introduction_transcript.pdf
PDF
create-netflix-clone-02-server_transcript.pdf
PDF
create-netflix-clone-04-server-continued_transcript.pdf
PDF
create-netflix-clone-01-introduction.pdf
PDF
create-netflix-clone-06-client-ui_transcript.pdf
PDF
create-netflix-clone-03-server.pdf
PDF
create-netflix-clone-04-server-continued.pdf
PDF
create-netflix-clone-05-client-model_transcript.pdf
PDF
create-netflix-clone-03-server_transcript.pdf
PDF
create-netflix-clone-02-server.pdf
PDF
create-netflix-clone-05-client-model.pdf
PDF
Creating a Whatsapp Clone - Part II.pdf
PDF
Creating a Whatsapp Clone - Part IX - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part II - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part V - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IV - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IV.pdf
PDF
Creating a Whatsapp Clone - Part I - Transcript.pdf
The Duck Teaches Learn to debug from the masters. Local to production- kill ...
create-netflix-clone-06-client-ui.pdf
create-netflix-clone-01-introduction_transcript.pdf
create-netflix-clone-02-server_transcript.pdf
create-netflix-clone-04-server-continued_transcript.pdf
create-netflix-clone-01-introduction.pdf
create-netflix-clone-06-client-ui_transcript.pdf
create-netflix-clone-03-server.pdf
create-netflix-clone-04-server-continued.pdf
create-netflix-clone-05-client-model_transcript.pdf
create-netflix-clone-03-server_transcript.pdf
create-netflix-clone-02-server.pdf
create-netflix-clone-05-client-model.pdf
Creating a Whatsapp Clone - Part II.pdf
Creating a Whatsapp Clone - Part IX - Transcript.pdf
Creating a Whatsapp Clone - Part II - Transcript.pdf
Creating a Whatsapp Clone - Part V - Transcript.pdf
Creating a Whatsapp Clone - Part IV - Transcript.pdf
Creating a Whatsapp Clone - Part IV.pdf
Creating a Whatsapp Clone - Part I - Transcript.pdf

Recently uploaded (20)

PDF
Chapter 3 Spatial Domain Image Processing.pdf
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PDF
Review of recent advances in non-invasive hemoglobin estimation
PDF
cuic standard and advanced reporting.pdf
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PDF
Empathic Computing: Creating Shared Understanding
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PPT
Teaching material agriculture food technology
PPTX
Machine Learning_overview_presentation.pptx
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
Electronic commerce courselecture one. Pdf
PPTX
Spectroscopy.pptx food analysis technology
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
DOCX
The AUB Centre for AI in Media Proposal.docx
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PDF
Spectral efficient network and resource selection model in 5G networks
Chapter 3 Spatial Domain Image Processing.pdf
The Rise and Fall of 3GPP – Time for a Sabbatical?
Dropbox Q2 2025 Financial Results & Investor Presentation
Review of recent advances in non-invasive hemoglobin estimation
cuic standard and advanced reporting.pdf
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Building Integrated photovoltaic BIPV_UPV.pdf
Empathic Computing: Creating Shared Understanding
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Per capita expenditure prediction using model stacking based on satellite ima...
Teaching material agriculture food technology
Machine Learning_overview_presentation.pptx
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
Electronic commerce courselecture one. Pdf
Spectroscopy.pptx food analysis technology
Agricultural_Statistics_at_a_Glance_2022_0.pdf
The AUB Centre for AI in Media Proposal.docx
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
Reach Out and Touch Someone: Haptics and Empathic Computing
Spectral efficient network and resource selection model in 5G networks

Creating an Uber Clone - Part XXVI - Transcript.pdf

  • 1. Creating an Uber Clone - Part XXVI Before we proceed into the actual driver app work & push notification we need to implement all the infrastructure in the server side. I also need to implement some changes we need in the protocol.
  • 2. private String hailingFrom; private String hailingTo; private String pushToken; public RideDAO getRideDao() { return new RideDAO(id, givenName, hailingFrom, hailingTo); } public UserDAO getDao() { return new UserDAO(id, givenName, surname, phone, email, facebookId, googleId, driver, car, currentRating, latitude, longitude, direction, pushToken); } public UserDAO getPartialDao() { return new UserDAO(id, givenName, surname, null, null, null, null, driver, car, currentRating, latitude, longitude, direction, pushToken); } User (Server) Lets start by looking at the User class. I had to add 3 new fields and modify/add some methods. hailingFrom & hailingTo allow us to communicate our trip details with the driver community
  • 3. private String hailingFrom; private String hailingTo; private String pushToken; public RideDAO getRideDao() { return new RideDAO(id, givenName, hailingFrom, hailingTo); } public UserDAO getDao() { return new UserDAO(id, givenName, surname, phone, email, facebookId, googleId, driver, car, currentRating, latitude, longitude, direction, pushToken); } public UserDAO getPartialDao() { return new UserDAO(id, givenName, surname, null, null, null, null, driver, car, currentRating, latitude, longitude, direction, pushToken); } User (Server) I need the pushToken of drivers so we can hail them directly from the app
  • 4. private String hailingFrom; private String hailingTo; private String pushToken; public RideDAO getRideDao() { return new RideDAO(id, givenName, hailingFrom, hailingTo); } public UserDAO getDao() { return new UserDAO(id, givenName, surname, phone, email, facebookId, googleId, driver, car, currentRating, latitude, longitude, direction, pushToken); } public UserDAO getPartialDao() { return new UserDAO(id, givenName, surname, null, null, null, null, driver, car, currentRating, latitude, longitude, direction, pushToken); } User (Server) I'll discuss the `RideDAO` class soon, it allows us to send details about the trip to drivers
  • 5. private String hailingFrom; private String hailingTo; private String pushToken; public RideDAO getRideDao() { return new RideDAO(id, givenName, hailingFrom, hailingTo); } public UserDAO getDao() { return new UserDAO(id, givenName, surname, phone, email, facebookId, googleId, driver, car, currentRating, latitude, longitude, direction, pushToken); } public UserDAO getPartialDao() { return new UserDAO(id, givenName, surname, null, null, null, null, driver, car, currentRating, latitude, longitude, direction, pushToken); } User (Server) Not much of a change but I added the pushToken to the UserDAO factory methods
  • 6. public class RideDAO implements Serializable { private long userId; private String name; private String from; private String destination; public RideDAO() { } public RideDAO(long userId, String name, String from, String destination) { this.userId = userId; this.name = name; if(this.name == null) { this.name = "[Unnamed User]"; } this.from = from; this.destination = destination; } // getters and setters trimmed out } RideDAO The RideDAO class is a pretty trivial object. There's no point in enumerating the details of this class as it's pretty simple. The main usage for this is in the new RideService which I will get to soon but first we need to discuss the Ride class.
  • 7. @Entity public class Ride { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @ManyToOne private User passenger; @ManyToOne private User driver; @OneToMany @OrderBy("time ASC") private Set<Waypoint> route; private BigDecimal cost; private String currency; private boolean finished; private boolean started; // trimmed out constructors, getters and setters } Ride Ride isn't as simple as RideDAO despite their common name. It contains far more information. Currently we don't use all of that but the fact that it's logged will let you provide all of that information within the app or a management app easily. The Ride class is a JPA entity similar to the User class. I used an auto-increment value for the id instead of a random string. I wanted to keep things simple but notice this can expose a security vulnerability of scanning for rides…
  • 8. @Entity public class Ride { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @ManyToOne private User passenger; @ManyToOne private User driver; @OneToMany @OrderBy("time ASC") private Set<Waypoint> route; private BigDecimal cost; private String currency; private boolean finished; private boolean started; // trimmed out constructors, getters and setters } Ride The passenger & driver are relational database references to the respective database objects representing each one of them
  • 9. @Entity public class Ride { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @ManyToOne private User passenger; @ManyToOne private User driver; @OneToMany @OrderBy("time ASC") private Set<Waypoint> route; private BigDecimal cost; private String currency; private boolean finished; private boolean started; // trimmed out constructors, getters and setters } Ride The route itself is a set of waypoints sorted by the time associated with the given waypoint. We'll discuss waypoints soon enough but technically it's just a set of coordinates
  • 10. @Entity public class Ride { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @ManyToOne private User passenger; @ManyToOne private User driver; @OneToMany @OrderBy("time ASC") private Set<Waypoint> route; private BigDecimal cost; private String currency; private boolean finished; private boolean started; // trimmed out constructors, getters and setters } Ride I really oversimplified the cost field. It should work for sum and currency but it's usually not as simple as that. It's important to use something like BigDecimal and not double when dealing with financial numbers as double is built for scientific usage and has rounding errors
  • 11. @Entity public class Ride { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @ManyToOne private User passenger; @ManyToOne private User driver; @OneToMany @OrderBy("time ASC") private Set<Waypoint> route; private BigDecimal cost; private String currency; private boolean finished; private boolean started; // trimmed out constructors, getters and setters } Ride We have two boolean flags, a ride is started once a passenger is picked up. It’s finished once he is dropped off or if the ride was canceled
  • 12. public interface RideRepository extends CrudRepository<Ride, Long> { @Query("select b from Ride b where b.finished = false and b.driver.id = ?1") public List<Ride> findByNotFinishedUser(long id); } RideRepository The companion CRUD RideRepository is pretty standard with one big exception. I added a special case finder that lets us locate the User that is currently hailing a car. Notice the syntax b.driver.id = ?1 which points through the relation to the driver object.
  • 13. @Entity public class Waypoint { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; private long time; private double latitude; private double longitude; private float direction; // trimmed out constructors, getters and setters } Waypoint The Waypoint entity referenced from the Ride entity is pretty trivial. Notice we still need a unique id for a waypoint even if we don't actually use it in code... The interesting part here is the time value which is the value of System.currentTimeMillis(). This allows us to build a path based on the time sequence. It will also allow us to reconstruct a trip and generate additional details such as speed/cost if we wish to do that in the future. Notice that there is also a WaypointRepository interface. I’m skipping it as it contains no actual code
  • 14. @Service public class RideService { @Autowired private UserRepository users; @Autowired private RideRepository rides; @Transactional public UserDAO hailCar(String token, boolean h, String from, String to) { User u = users.findByAuthToken(token).get(0); if(h) { if(u.getAssignedUser() != null) { long driverId = u.getAssignedUser(); u.setAssignedUser(null); users.save(u); User driver = users.findOne(driverId); return driver.getPartialDao(); } } else { u.setAssignedUser(null); } u.setHailing(h); u.setHailingFrom(from); u.setHailingTo(to); users.save(u); return null; } RideService The RideService class serves the same purpose as the UserService class focusing on rides and driver related features. I could have just stuck all of this logic into one huge class but separating functionality to different service classes based on logic makes sense. We manipulate both the rides and users CRUD objects from this class
  • 15. @Service public class RideService { @Autowired private UserRepository users; @Autowired private RideRepository rides; @Transactional public UserDAO hailCar(String token, boolean h, String from, String to) { User u = users.findByAuthToken(token).get(0); if(h) { if(u.getAssignedUser() != null) { long driverId = u.getAssignedUser(); u.setAssignedUser(null); users.save(u); User driver = users.findOne(driverId); return driver.getPartialDao(); } } else { u.setAssignedUser(null); } u.setHailing(h); u.setHailingFrom(from); u.setHailingTo(to); users.save(u); return null; } RideService Hailing is a transactional method, this means that all operations within the method will either succeed or fail depending on the outcome. This is important to prevent an inconsistent state in the database
  • 16. @Service public class RideService { @Autowired private UserRepository users; @Autowired private RideRepository rides; @Transactional public UserDAO hailCar(String token, boolean h, String from, String to) { User u = users.findByAuthToken(token).get(0); if(h) { if(u.getAssignedUser() != null) { long driverId = u.getAssignedUser(); u.setAssignedUser(null); users.save(u); User driver = users.findOne(driverId); return driver.getPartialDao(); } } else { u.setAssignedUser(null); } u.setHailing(h); u.setHailingFrom(from); u.setHailingTo(to); users.save(u); return null; } RideService This method can be invoked to start and stop hailing. In this case we use the assigned user property to detect if a driver accepted the ride. If so we return the driver data to the client
  • 17. users.save(u); return null; } public RideDAO getRideData(long userId) { User u = users.findOne(userId); if(u == null) { return null; } return u.getRideDao(); } @Transactional public long acceptRide(String token, long userId) { User driver = users.findByAuthToken(token).get(0); User passenger = users.findOne(userId); if(!passenger.isHailing()) { throw new RuntimeException("Not hailing"); } passenger.setHailing(false); passenger.setAssignedUser(driver.getId()); driver.setAssignedUser(userId); users.save(driver); users.save(passenger); Ride r = new Ride(); r.setDriver(driver); r.setPassenger(passenger); rides.save(r); RideService When a driver gets a notification of a ride he invokes this method to get back the data about the ride
  • 18. users.save(u); return null; } public RideDAO getRideData(long userId) { User u = users.findOne(userId); if(u == null) { return null; } return u.getRideDao(); } @Transactional public long acceptRide(String token, long userId) { User driver = users.findByAuthToken(token).get(0); User passenger = users.findOne(userId); if(!passenger.isHailing()) { throw new RuntimeException("Not hailing"); } passenger.setHailing(false); passenger.setAssignedUser(driver.getId()); driver.setAssignedUser(userId); users.save(driver); users.save(passenger); Ride r = new Ride(); r.setDriver(driver); r.setPassenger(passenger); rides.save(r); RideService If the driver wishes to accept the ride he invokes this transactional method. The method accepts the token from the driver and the id of the user hailing the ride. It creates a new Ride entity and returns its ID, from this point on we need to refer to the Ride id and not the user id or token
  • 19. User driver = users.findByAuthToken(token).get(0); User passenger = users.findOne(userId); if(!passenger.isHailing()) { throw new RuntimeException("Not hailing"); } passenger.setHailing(false); passenger.setAssignedUser(driver.getId()); driver.setAssignedUser(userId); users.save(driver); users.save(passenger); Ride r = new Ride(); r.setDriver(driver); r.setPassenger(passenger); rides.save(r); return r.getId(); } public void startRide(long rideId) { Ride current = rides.findOne(rideId); current.setStarted(true); rides.save(current); } public void finishRide(long rideId) { Ride current = rides.findOne(rideId); current.setFinished(true); rides.save(current); } } RideService Start ride and finish ride are invoked by the driver when he picks up the passenger and when he drops him off. Normally, finish ride should also handle elements like billing etc. but I won't go into that now
  • 20. @Controller @RequestMapping("/ride") public class RideWebservice { @Autowired private RideService rides; @RequestMapping(method=RequestMethod.GET,value = "/get") public @ResponseBody RideDAO getRideData(long id) { return rides.getRideData(id); } @RequestMapping(method=RequestMethod.GET,value="/accept") public @ResponseBody String acceptRide(@RequestParam(name="token", required = true) String token, @RequestParam(name="userId", required = true) long userId) { long val = rides.acceptRide(token, userId); return "" + val; } @RequestMapping(method=RequestMethod.POST,value="/start") public @ResponseBody String startRide(@RequestParam(name="id", required = true) long rideId) { rides.startRide(rideId); return "OK"; } @RequestMapping(method=RequestMethod.POST,value="/finish") public @ResponseBody String finishRide(@RequestParam(name="id", required = true) long rideId) { rides.finishRide(rideId); return "OK"; } } RideWebservice The next step is bridging this to the user through a webservice... The RideWebservice class exposes the RideService calls almost verbatim to the client. The get call fetches the RideDAO for the given user id
  • 21. @Controller @RequestMapping("/ride") public class RideWebservice { @Autowired private RideService rides; @RequestMapping(method=RequestMethod.GET,value = "/get") public @ResponseBody RideDAO getRideData(long id) { return rides.getRideData(id); } @RequestMapping(method=RequestMethod.GET,value="/accept") public @ResponseBody String acceptRide(@RequestParam(name="token", required = true) String token, @RequestParam(name="userId", required = true) long userId) { long val = rides.acceptRide(token, userId); return "" + val; } @RequestMapping(method=RequestMethod.POST,value="/start") public @ResponseBody String startRide(@RequestParam(name="id", required = true) long rideId) { rides.startRide(rideId); return "OK"; } @RequestMapping(method=RequestMethod.POST,value="/finish") public @ResponseBody String finishRide(@RequestParam(name="id", required = true) long rideId) { rides.finishRide(rideId); return "OK"; } } RideWebservice Start and finish rides are again very simple with only one argument which is the ride id
  • 22. public void updatePushToken(String token, String pushToken) { User u = users.findByAuthToken(token).get(0); u.setPushToken(pushToken); users.save(u); } UserService We also have to add some minor changes to the UserService and LocationService classes. Lets start with the UserService class. Drivers need a push token so we can hail them. This is always set outside of the user creation code for two reasons. The first time around the user is created but the push key isn't there yet (it arrives asynchronously) Push is re-registered in every launch and refreshed, there is no reason to update the entire object for that
  • 23. @RequestMapping(method = RequestMethod.GET,value = "/setPushToken") public @ResponseBody String updatePushToken( @RequestParam(name="token", required = true) String token, @RequestParam(name="pushToken", required = true) String pushToken) { users.updatePushToken(token, pushToken); return "OK"; } UserWebservice The UserWebservice class needs to mirror these changes obviously... There isn't much here we just added a new setPushToken URL and we accept this update.
  • 24. public class LocationService { @Autowired private UserRepository users; @Autowired private RideRepository rides; @Autowired private WaypointRepository waypoints; public void updateUserLocation(String token,double lat,double lon,float dir) { List<User> us = users.findByAuthToken(token); User u = us.get(0); u.setLatitude(lat); u.setLongitude(lat); u.setDirection(dir); users.save(u); if(u.isDriver() && u.getAssignedUser() != null) { List<Ride> r = rides.findByNotFinishedUser(u.getId()); if(r != null && !r.isEmpty()) { Ride ride = r.get(0); if(ride.isStarted() && !ride.isFinished()) { Set<Waypoint> route = ride.getRoute(); Waypoint newPosition = new Waypoint( System.currentTimeMillis(), lat, lon, dir); waypoints.save(newPosition); route.add(newPosition); ride.setRoute(route); rides.save(ride); LocationService The LocationService needs a bit more work. Every time we update a users location we check if he's a driver on a ride
  • 25. public class LocationService { @Autowired private UserRepository users; @Autowired private RideRepository rides; @Autowired private WaypointRepository waypoints; public void updateUserLocation(String token,double lat,double lon,float dir) { List<User> us = users.findByAuthToken(token); User u = us.get(0); u.setLatitude(lat); u.setLongitude(lat); u.setDirection(dir); users.save(u); if(u.isDriver() && u.getAssignedUser() != null) { List<Ride> r = rides.findByNotFinishedUser(u.getId()); if(r != null && !r.isEmpty()) { Ride ride = r.get(0); if(ride.isStarted() && !ride.isFinished()) { Set<Waypoint> route = ride.getRoute(); Waypoint newPosition = new Waypoint( System.currentTimeMillis(), lat, lon, dir); waypoints.save(newPosition); route.add(newPosition); ride.setRoute(route); rides.save(ride); LocationService Assuming we have a Ride object we check if this is currently an ongoing ride that wasn't finished
  • 26. public class LocationService { @Autowired private UserRepository users; @Autowired private RideRepository rides; @Autowired private WaypointRepository waypoints; public void updateUserLocation(String token,double lat,double lon,float dir) { List<User> us = users.findByAuthToken(token); User u = us.get(0); u.setLatitude(lat); u.setLongitude(lat); u.setDirection(dir); users.save(u); if(u.isDriver() && u.getAssignedUser() != null) { List<Ride> r = rides.findByNotFinishedUser(u.getId()); if(r != null && !r.isEmpty()) { Ride ride = r.get(0); if(ride.isStarted() && !ride.isFinished()) { Set<Waypoint> route = ride.getRoute(); Waypoint newPosition = new Waypoint( System.currentTimeMillis(), lat, lon, dir); waypoints.save(newPosition); route.add(newPosition); ride.setRoute(route); rides.save(ride); LocationService If so we add a waypoint to the ride and update it so we can later on inspect the path of the Ride. This pretty much tracks rides seamlessly. If we wanted to be really smart we could detect the driver and user position to detect them traveling together and automatically handle the ride. There are obviously problems with this as it means a user can't order a cab for someone else but it might be an interesting feature since we have two close data points…