<body><script type="text/javascript"> function setAttributeOnload(object, attribute, val) { if(window.addEventListener) { window.addEventListener('load', function(){ object[attribute] = val; }, false); } else { window.attachEvent('onload', function(){ object[attribute] = val; }); } } </script> <div id="navbar-iframe-container"></div> <script type="text/javascript" src="https://apis.google.com/js/platform.js"></script> <script type="text/javascript"> gapi.load("gapi.iframes:gapi.iframes.style.bubble", function() { if (gapi.iframes && gapi.iframes.getContext) { gapi.iframes.getContext().openChild({ url: 'https://www.blogger.com/navbar.g?targetBlogID\x3d8334277\x26blogName\x3dSriram\x27s+Blog\x26publishMode\x3dPUBLISH_MODE_BLOGSPOT\x26navbarType\x3dBLUE\x26layoutType\x3dCLASSIC\x26searchRoot\x3dhttps://metallicatony.blogspot.com/search\x26blogLocale\x3den\x26v\x3d2\x26homepageUrl\x3dhttps://metallicatony.blogspot.com/\x26vt\x3d8734955176758301540', where: document.getElementById("navbar-iframe-container"), id: "navbar-iframe", messageHandlersFilter: gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER, messageHandlers: { 'blogger-ping': function() {} } }); } }); </script>

Tuesday, April 03, 2018

Competitive Analysis - A case study on ticket market places

What is competitive analysis?
Competitive analysis is the trade of analyzing your business, products, features and various other metrics against your competitors. This is a continuous task done for the entire product lifecycle essentially to understand the competitive landscape and to position your products better in the market. To perform an effective research, you need a pretty good understanding about your own product before you can research about your competitors.

Competitive analysis is also useful for new products that you are considering to build in the future. By researching about the competitive offering of similar products, you tend to get a better rationale on all or some of the below points

1) What is the problem being solved and what is the value proposition for the customer and the organization
2) What types of target customer segments are you looking at vs your competitors
3) What is the market size estimation for the product? What could be the fiscal opportunity based on a calculated market penetration
4) How big and great you want to build your product on a competitor relative scale
5) What are the minimum viable product (MVP) features you want to include during launch
6) What are the existing issues in competitor offerings based on customer feedback and the data gathered from social channels
And there are much more that you can do by researching your competitors. By doing this research, you will also be able to conclude on product differentiation, meaning, how you would like your product to differentiate against your competitors in terms of various dimensions like target segment, value, features and investment


Types of competitors
There are different types of competitors for any kind of business. They are categorized based on the type of offering and the customer segment they serve. Categorizing them helps you to clearly understand the level of intersection that you have with your competitors and understand future threats if any.

     a) Direct competitors
     These are competitors who are solving the same problem for the same set of customers that you are solving for

     b) Indirect competitors
     These are competitors that are solving the same problem in a different way. Their customer segment intersects with yours or it could be a subset or a superset of yours entirely.

     c) Potential competitors
     These are competitors who address your type of customers as well but they are solving a different problem for them. However, these competitors pose the threat of becoming your direct or indirect competitor at any point in the future.

Know the pluses and minuses of your competitors
While doing a competitor research, it is important to understand the positives and negatives of competitors and their products. This helps to comprehend on the strengths, weaknesses and the next set of moves from the competitors. It is also good to research about the leaders in the competitor organizations so that you get a better picture of their experience and insights which might potentially translate into future vision.

Knowing precisely about the plans of your competitors in current and next few years is valuable. It is also good to compare yourself with your competitors on dimensions like GMV, revenue, size of user base, brand name, public opinion, design and user experience, prices, agility and intellectuality of engineering team etc.

Feature table
Feature table is a simple but powerful in assisting to perform competitive analysis using all the above said dimensions. It is also useful to list and plan out the features you want to build out by knowing clearly who is offering what. Feature table is nothing but a matrix of a bunch of items in the Y axis and a bunch of competitors in the X axis. Based on the feature in consideration, you have to say Yes or No for every competitor based on whether they are offering. It is important to keep the feature description concise and take a note of pluses and minuses of the competitor with respect to the feature.

Case Study on ticket market places
I have done a case study of the event ticketing market landscape by comparing my organization - StubHub - against our competitors. Instead of focusing on all the zillion aspects, I wanted to limit the research to only a core set of features or products that is available for the users in the web experience. Also the below research focuses only on the buyer segment and not the seller segment of the market. It does not focus on the user experience or design but rather focuses on the product features that are available for the users. However, it is imperative to consider all the business and product dimensions for an exhaustive research.

S.No Features Ticketmaster Stubhub Vivid Seats LiveNation/TicketsNow/TicketWeb SeatGeek
1 Ability to enter promotional codes in event page and see discounted ticket prices Yes No No No No
2 Show American express points in event page Yes No No No No
3 Mix both primary and resale tickets in event page listings Yes Yes but for few handful events (76ers) No No No
4 3D collector tickets featuring 3D graphics of personalities that acts as both ticket and collectible Yes No No No No
5 Ability to add merchandise items to the order - wand, sword, wrist bands, Lanyards, customized tickets, drink tickets,   etc Yes No No No No
6 Ability to add Parking Pass along with ticket No Yes No No No
7 Select delivery - fast or slow and accordingly charge delivery fee Yes No Yes Yes No
8 Let users to Guest checkout and purchase tickets No No Yes - Allows first time buyers to purchase tickets and set the password for their account post transaction No No
9 Allow buyers to use Amex points for purchasing tickets Yes No No Yes No
10 Promote official payment method AMEX and give back rewards or points for customers for the transactions made using AMEX Yes No No No No
11 Support more less common payment methods - Amex express checkout, Diners club, master pass etc Yes No No No No
12 Provide ability for Buyers to insure their tickets for an additional amount - for eg: Ticketmaster partners with Allianz Insurance company so that buyers can have their ticket cost reimbursed in case they could not go for the event Yes - Allianz Insurance No Yes - Allianz Insurance Yes - Allianz Insurance No
13 Show Preferred seller listings in event page listings - Preferred sellers are longstanding partners with the ticket marketplace that provide exceptional inventory and service No No Yes No No
14 Run promotions for the current occasion in home page. For eg. Valentine promotion in home page (2 for 1 tickets)  takes the users to different event selector page to choose events that are elgibile for valentine promotion Yes No No Yes No
15 Flex pack tickets - tickets for some of the events are sold for a discounted price in ticket deals page, only a bunch of events are eligible for flex pack tickets Yes No Yes - Vivid Value $ for buying packaged tickets No No
16 VIP packages - The VIP package includes a premium seat, special souvenir gift and exclusive access to theater characters
Sports Packages - Tickets with sports merchandise and pregame perks
Theater Packages and family packages
Some packages include discounts if a bunch of tickets are purchased
Yes No Yes No No
17 Are Tickets secured for customers for a limited time with a counter running in the page Yes No No Yes No
18 Provide more information to fans and build your brands expertiese and credibility - For example blog about price trends, demand of current events, about the most famous artists and their forthcoming events, share news and experiences of fans. Search engines love new content and rank such sites much better Yes (Ticketmaster Insider) No Yes - Fandemonium vivid blog Yes (Ticketmaster Insider) Yes - SeatGeek Event News
19 Corporate Ticket Program - takes care of tickets, hotel packages and other hospitality programs No No Yes No No
20 Affiliate Program to drive traffic from partner sites to marketplace. Partner earns commission on transactions and payments are distributed on a monthly basis No No Yes Yes No
21 Lists all the on sale events in home page Yes Yes - not all on sale events but some rotate in the banner Yes Yes No
22 Youtube Channel that hosts fan experience of events - captures fan reactions and experiences Yes Yes - but not very active Yes - Fandemonium vivid blog Yes No
23 Price stats and guidance - drop or up for an event overall. Notify customers by email to keep them informed how an event market looks like Yes Yes - using price assistant No Yes No
24 Single Order Lookup to get order details without asking buyers to login No No Yes - uses order id and order email to lookup order details No No
25 Sell a set of tickets from selected sections through deal aggregator sites - groupon, livingsocial, facebook etc Yes No No No Yes
26 Newsletter for fans about events in their city No No No Yes No
27 Presales done leveraging partnership with payment providers, fan clubs etc Yes On sale is available but not presale No Yes No
28 Connected Services - Import favorite artists from FB, Spotify, Pandora into web my account Yes Yes -  can scan mobiles itunes, can  import friends and their favorites from FB No Yes Yes
29 Gift cards - eGift and Plastic Gift cards Yes Yes Yes - Promo codes only Yes Yes - Promo codes only
30 Top Concerts, Top Sports & Top theater tickets Yes No Yes Yes Yes

Labels: , ,

Wednesday, January 17, 2018

Spring Boot application hosted in Pivotal Cloud Foundry integrated with AWS hosted service

This post talks about a simple spring boot app that i developed to play around with pivotal cloud foundry and Amazon AWS.

SimpleService is a spring boot application using which the following objectives are accomplished
1) How to build and deploy a cloud-native app in Pivotal Cloud Foundry (PaaS)
2) How to host a custom service (oracle) in Amazon AWS Cloud (IaaS)
3) How to create a CUPS service in PCF and deliver service credentials
4) How to integrate a PCF app with an AWS hosted service


Github repository of the source is here: https://github.com/metallicatony/SimpleService/

Step 1:
Create a AWS free tier account and install a db.t2.micro oracle database standard edition instance that comes as part of free tier. Setup the security group rules in such a way that it allows inbound and outbound connections from outside world.

Step 2:
Create a spring boot application by exposing a bunch of services. Use the db credentials from the previous step. The source code from this repository helps for this step. It demonstrates the use of spring boot, spring data and repository concepts.

Step 3:
* Create a PCF free tier account, org and space in your account
* Download and install cf cli tools in your development environment
* cf login with your credentials
* cf push with a manifest file already provided as part of the source or manually push by using cf push SimpleServiceApp -p target/simpleservice-1.0.0.jar -b https://github.com/cloudfoundry/java-buildpack.git

Step 4:
* Create user provided service instance in pivotal cloud foundry to deliver the uri and service credentials cf cups oracle-aws -p '{"uri":"oracle://admin:xxx@myoracleinstance.c9anhdk2mqpc.us-west-1.rds.amazonaws.com:1521/ORCL"}' * Validate in PCF dashboard

Step 5:
Bind the oracle-aws service with SimpleServiceApp either through manifest file or manually by using cf bind-service SimpleServiceApp oracle-aws. Either restage or push your app again to PCF.

Step 6:
Test the app by using a browser or rest client https://simpleserviceapp.cfapps.io/applications/

Wednesday, May 13, 2015

REST APIs to perform Geospatial operations using MongoDB, Google Geocoding API, GeoJSON and Spring Data

This project is an extension to the previous project “How to build CRUD APIs using Apache CXF, Spring Data and MongoDB to manage a set of employees”. The objective of this project is to extend the APIs that were built out of the previous project, to deal mainly with employees’ address information. To accomplish this objective, it utilizes and thereby demonstrates the power of MongoDB’s geospatial features, Google’s geocoding apis and GeoJSON. Some of the real world use cases like the below are something that is possible using this project

a) List all employees that live within a 50 mile radius of a given address
b) List all employees that live within a given set of polygon co-ordinates

Before proceeding further with the implementation, here is a brief introduction about the supporting geo-technologies used in this project

GeoJSON
GeoJson is an open standard format developed to represent spatial geometries using standard JSON name/value pair convention. Geometries could be a simple spatial Point, LineString or a complex Polygon. In addition that, GeoJson also supports geo-features and feature collections. A “geo-feature” is typically a geometry combined with its additional properties and a feature collection is a multitude of features bundled as a single representation.

A geometry object mostly contains two members – “type” and “coordinates”. Type represents the name of the geojson object and coordinates represents an array of longitude and latitude pairs. There can be many such co-ordinate pairs depending on the type of geometry being represented. A couple of sample GeoJSONs are shown below
{ "type": "Point", "coordinates": [100.0, 0.0] }
{ "type": "Polygon",
    "coordinates": [
      [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ]
     ]
}
More about GeoJson and its examples can be found here GeoJSON Specification - http://geojson.org/geojson-spec.html#appendix-a-geometry-examples

MongoDB’s Geospatial features
MongoDB supports a handful of geospatial operations on both Euclidean plane and spherical surface. These operations are possible because it is equipped with two types of geo-indexes – 2dindex and 2dsphere. 2d index supports operations on an Euclidean plane and allows mongo documents to have geo-information as legacy co-ordinates [latitude, longitude]. 2dsphere index supports operations on earth like sphere and supports geo-data as both legacy co-ordinates and as GeoJSON.

Based on the type of operations that might require for an application, a choice needs to be made from either of two structures and that in turn will decide the type of geo-index that needs to be used. More about the different types of operators available in MongoDB is over here MongoDB GeoSpatial Query operators - http://docs.mongodb.org/manual/reference/operator/query-geospatial

Google’s geocoding Api
Geocoding is the process of converting a regular postal address into geographic co-ordinates. Reverse geocoding is the reverse process of converting geo coordinates back to human readable addresses. Using these processes, it is possible to encode addresses to coordinates and perform geo-operations on the coordinates.

More about Google’s Geocoding API can be found here Google developer site - Geocoding - https://developers.google.com/maps/documentation/geocoding/. An OAuth application (Google calls it project) has to be created and activated in Google's developer site to use this api. The server key of the created OAuth project will be used in our sample application to geocode addresses.

Having talked about the geo-technologies used in this project, let’s move on to discuss about how to incorporate these features and perform a real world usecase of "finding employees who work within a given radius of 50 miles from a given address".

Modifications to MongoDB document structure
As the employee’s work address information needs to be persisted along with the rest of employee information, the existing “employees” mongo document structure (the one that was used here “How to build CRUD APIs using Apache CXF, Spring Data and MongoDB to manage a set of employees”) is extended to comprise the "address" geojson. The existing documents in the store are updated to have their corresponding employee's address so that they qualify as candidates for the usecase that we are working towards. In addition to that, the documents that will be created in future needs to have the address information. Again, it is not mandatory that every employee’s document needs to contain their work address, but if they have, such documents will qualify as a candidate for geo operations.

The “address” object in our case is not a straight forward geojson. It contains two other objects – “postalAddress” and “location”. Postal address carries the work address of the employee. Location is the actual GeoJson object that carries the geocoded co-ordinate information of the employee work address. These two objects are bundled together into a single address object. Below is a sample address json object
"address" :{
    "loc": {
        "type": "Point",
        "coordinates": [
            -122.0829,
            37.4211
        ]
    },
    "postalAddress": {
        "street": "1600 Amphitheatre Parkway",
        "city": "Mountain View",
        "state": "California",
        "zip": "94043",
        "country": "UnitedStates"
    }
}
Following mongo query inserts documents that includes the above address structure into the employees collection of organization database
> use organization;
> db.employees.insert({
    "empId": NumberLong(35),
    "fname": "Richard",
    "lname": "Stallman",
    "deptName": "CS",
    "salary": 1000,
    "address": {
        "loc": {
            "type": "Point",
            "coordinates": [
                -122.3947492,
                37.7899519
            ]
        },
        "postalAddress": {
            "street": "199 Fremont Street",
            "city": "San Francisco",
            "state": "CA",
            "zip": "94105",
            "country": "United States"
        }
    }
});

Once we have a handful of employees along with their work address in mongo store, it is now time to query them using mongo console (or any favorite mongo client of yours) to find whether they are really query-able. MongoDB cannot support any geo-operations without having a geo-index. If done so, it says there is no index. Below is a snippet to illustrate that

Query before Indexing
> db.employees.find({
    "address.loc": {
        $near: {
            $geometry: {
                type: "Point",
                coordinates: [
                    -122.08,
                    37.42
                ]
            },
            $maxDistance: 80467.2
        }
    }
});
error: {
        "$err" : "Unable to execute query: error processing query: ns=organization.employees limit=0 skip=0\nTree: GEONEAR  
field=loc maxdist=35000 isNearSphere=0\nSort: {}\nProj: {}\n planner returned error: unable to find index for $geoNear query",
        "code" : 17007
}

Create a 2dsphere geo index
A 2dsphere index is created on the GeoJSON object. In this case, it is “address.loc” which is an embedded document.
> db.employees.createIndex({"address.loc": "2dsphere"});
{
    "createdCollectionAutomatically": false,
    "numIndexesBefore": 1,
    "numIndexesAfter": 2,
    "ok": 1
}

> db.employees.getIndexes();
[
    {
        "v": 1,
        "key": {
            "_id": 1
        },
        "name": "_id_",
        "ns": "organization.employees"
    },
    {
        "v": 1,
        "key": {
            "address.loc": "2dsphere"
        },
        "name": "address.loc_2dsphere",
        "ns": "organization.employees",
        "2dsphereIndexVersion": 2
    }
]

Query after Indexing
After creating a 2dsphere index, the same geo query operation is successful
> db.employees.find({
    "address.loc": {
        $near: {
            $geometry: {
                type: "Point",
                coordinates: [
                    -122.08,
                    37.42
                ]
            },
            $maxDistance: 80467.2
        }
    }
});

{
    "_id": ObjectId("55227f844beece3fbb066d7e"),
    "empId": NumberLong(33),
    "fname": "Sundar",
    "lname": "Pichai",
    "deptName": "CS",
    "salary": 1000,
    "address": {
        "loc": {
            "type": "Point",
            "coordinates": [
                -122.0829,
                37.4211
            ]
        },
        "postalAddress": {
            "street": "1600 Amphitheatre Parkway",
            "city": "Mountain View",
            "state": "California",
            "zip": "94043",
            "country": "United States"
        }
    }
}
The next set of steps is to make modifications to our java Crud apis to support and utilize these features

POM changes
The POM of this project is almost the same like the previous project apart from the fact that this one includes google’s geocoding library. Below is a snippet to do that
0.1.6
 
  com.google.maps
  google-maps-services
  ${google.maps.services.version}
 

Create Employee
The create employee api request is added with a new property called “Address”. This address will represent the work address of the employee. The below implementation geo-codes this given address into latitude and longitude information and then persisted into mongo store using the above document structure.
public EmployeeResponse createEmployee(EmployeeRequest employeeRequest) throws AddressException, Exception {
 EmployeeResponse employeeResponse = null;
 Employee employee = null;
 Double[] latlng = null;
 
 if (employeeRequest != null) {
  Long empId = employeeRepository.getNextId();
  log.info("new employeeId={}", empId);

  latlng = employeeHelper.getGeoResult(employeeRequest.getAddress());
  employee = employeeAdapter.buildDocument(empId, employeeRequest, latlng);
  employeeRepository.createEmployee(employee);
  if (employee.get_id() != null) {
  employeeResponse = employeeAdapter.convertToEmployeeResponse(employee);
  }
 }
 
 log.info("employeeResponse={}", employeeResponse);
 return employeeResponse;
}

The given postal address is converted to a string representation which is then used by the geocode call. The geocode call also uses something called as “geocontext”. It is the context created using the server key that was generated from your OAuth project in google’s developer site.

private static final GeoApiContext geoContext = new GeoApiContext().setApiKey("xxx");


/**
 * Gets geo information for a given postal address using google geocoding api
 * @param address the address information
 * @return GeocodingResult[] an array of GeocodingResult
 * @throws Exception
 */
public Double[] getGeoResult(PostalAddress pa) throws AddressException, Exception {
 log.info("get geoinfo for address={}", pa);

 if (pa != null) {
 String address = employeeAdapter.convertPostalAddressToTextAddress(pa);
 return geoCode(address);
 }
 return null;
}

 /**
  * Gets geo information for a given address using google geocoding api
  * @param address
  * @return Double[]
  * @throws AddressException
  * @throws Exception
  */
 public Double[] geoCode(String address) throws AddressException, Exception {
  log.info("geocode address={}", address);
  GeocodingResult[] geoResult = null;
  
  geoResult =  GeocodingApi.geocode(geoContext, address).await();
  // if no matches or more than 1 match throw address exception
  if (geoResult == null
    || geoResult.length != 1
    //|| geoResult[0].geometry.locationType != LocationType.ROOFTOP
    ) {
   throw new AddressException("INVALID_ADDRESS");
  }
  log.info("received geoInfo={}", geoResult[0].geometry.location);
  return new Double[] {geoResult[0].geometry.location.lat, geoResult[0].geometry.location.lng};
 }

Get and Get all employees
The most interesting usecase in get and get all employees apis is to find out all the employees in the organization living within a given radius (in miles). To elaborate a bit more, this operation finds all employees who live within a given radius of a circle whose center falls at the address that was provided in the request. This is accomplished by geo-coding the provided address into lat-long co-ordinates using geocoding api. Once we have the co-ordinates, they can be used to query mongodb using $near query operator in such a way that those co-ordinates are the center of a circle. The $near query operator in turn uses the geospatial index created on the "employees document collection" to find whether such an employee exists.

else if (latlng != null && lname == null) {
   // get employees that live within a given radius of the given address
   NearQuery nq = NearQuery.near(latlng[1], latlng[0], Metrics.MILES).maxDistance(new Double(radius));
   GeoResults<Employee> empGeoResults = mongoTemplate.geoNear(nq, Employee.class);
   if (empGeoResults != null) {
    empList = new ArrayList<Employee>();
    for (GeoResult<Employee> e : empGeoResults) {
     empList.add(e.getContent());
    }
   }

The below shown code snippet is to find all employees that have a given last name and live within a given radius (in miles) of the given address. The below method is different in a way where the query is completely created manually using Spring data's BasicDBObjectBuilder. The fetched DBObject is iterated using DBCursor and converted to "Employee" mongo document automatically using MongoConverter.
else if (latlng != null && lname != null) {
   // get employees that have a given last name and live within a given radius of the given address
   DBObject geoQuery = buildGeoQuery(latlng, radius);
   DBObject addressLoc = BasicDBObjectBuilder.start().add("address.loc", geoQuery).add("lname", lname).get();
   DBCursor cursor = mongoTemplate.getCollection("employees").find(addressLoc);
   empList = cursor.hasNext() ? new ArrayList<Employee>() : null;
   while (cursor.hasNext()) {
    DBObject empDBObject = (DBObject)cursor.next();
    Employee e = mongoTemplate.getConverter().read(Employee.class, empDBObject);
    empList.add(e);
   }
  }


 /**
  * Builds geo query using latitude, longitude co-ordinates and radius. Given
  * radius is in miles and needs to be converted into meters.
  * @param lname
  * @param latlng
  * @param radius
  * @return
  */
 private DBObject buildGeoQuery(Double[] latlng, String radius) {
  // Mongo uses longitude, latitude combination
  Double[] coordinates = new Double[] {latlng[1], latlng[0]};
  DBObject geometryContent = BasicDBObjectBuilder.start().add("type", "Point")
    .add("coordinates", coordinates).get();
  DBObject nearContent = BasicDBObjectBuilder.start().add("$geometry", geometryContent)
    .add("$maxDistance", (Double.valueOf(radius) * 1609.34)).get();
  DBObject addressLocContent = BasicDBObjectBuilder.start().add("$near", nearContent).get();
  return addressLocContent;
 }

The complete source code of this project has been hosted in my GitHub repository – REST APIs to perform geospatial operations using MongoDB. Please feel free to peek and play around.

Labels: ,