5
\$\begingroup\$

I have a library which is running in different datacenters in production. I have three datacenters in production:

DHP, SLP, LTR

Depending on which datacenter code is running, I need to return two string one is LOCAL_STRING and other is REMOTE_STRING.

Our machine name is like this in production and it is also possible that machine hostname can have more dots in between separated by another domain in future.

dbx111.dhp.host.com (machine in dhp datacenter)
dbx112.slp.host.com (machine in slp datacenter)
dcx113.ltr.host.com (machine in ltr datacenter)

And our machine name in DEV (our dev environment) is like this:

z-wejnv-0432.dev.host.com

Below are my use case:

For code running in production case and if the DataFlowEnum is RECO:

  • If it is running in DHP datacenter, then LOCAL_STRING should be /tr/datacenter/dhp and REMOTE_STRING will be chosen randomly between SLP and TLR, so it can be /tr/datacenter/slp or /tr/datacenter/ltr.
  • Similarly If it is running in SLP datacenter, then LOCAL_STRING should be /tr/datacenter/slp and REMOTE_STRING will be chosen randomly between DHP and TLR, so it can be /tr/datacenter/dhp or /tr/datacenter/ltr.
  • Similarly If it is running in LTR datacenter, then LOCAL_STRING should be /tr/datacenter/ltr and REMOTE_STRING will be chosen randomly between DHP and SLP, so it can be /tr/datacenter/dhp or /tr/datacenter/slp.

For code running in production case and if the DataFlowEnum is UNRECO: only difference is instead of tr, I will use trpp.

  • If it is running in DHP datacenter, then LOCAL_STRING should be /trpp/datacenter/dhp and REMOTE_STRING will be chosen randomly between SLP and TLR, so it can be /trpp/datacenter/slp or /trpp/datacenter/ltr.
  • Similarly If it is running in SLP datacenter, then LOCAL_STRING should be /trpp/datacenter/slp and REMOTE_STRING will be chosen randomly between DHP and TLR, so it can be /trpp/datacenter/dhp or /trpp/datacenter/ltr.
  • Similarly If it is running in LTR datacenter, then LOCAL_STRING should be /trpp/datacenter/ltr and REMOTE_STRING will be chosen randomly between DHP and SLP, so it can be /trpp/datacenter/dhp or /trpp/datacenter/slp.

If the code is not running in production or it is running in DEV, then LOCAL_STRING and REMOTE_STRING should always be /tr/datacenter/dhp if DataFlowType is RECO or /trpp/datacenter/dhp if if DataFlowType is UNRECO.

Below is my DataCenterEnum:

public enum DatacenterEnum {
    DEV, DHP, SLP, LTR;

    private static final Random random = new Random();

    public static String forCode(int code) {
        return (code >= 0 && code < values().length) ? values()[code].name() : null;
    }

    private static final DatacenterEnum ourlocation = compareLocation();

    private static DatacenterEnum compareLocation() {
        String ourhost = getHostNameOfServer();

        if (ourhost != null) {
            if (isDevHost(ourhost)) {
                return DHP;
            }

            for (DatacenterEnum dc : values()) {
                String namepart = "." + dc.name().toLowerCase() + ".";
                if (ourhost.indexOf(namepart) >= 0) {
                    return dc;
                }
            }

            // this means the call is coming from some other datacenter apart from DHP, SLP and LTR, 
            // so we will randomly select DHP, SLP or LTR
            return DataUtils.isProduction() ? DatacenterEnum.values()[random.nextInt(3) + 1] : DatacenterEnum
                    .values()[random.nextInt(1) + 1];
        }

        // if it comes here then it means somehow, we failed to find the hostname.
        return DataUtils.isProduction() ? DatacenterEnum.values()[random.nextInt(3) + 1]
                : DatacenterEnum.values()[random.nextInt(1) + 1];
    }

    private String toLocalString() {
        if (this == DEV || !(DataUtils.isProduction())) {
            return "/datacenter/dhp";
        }

        return "/datacenter/" + name().toLowerCase();
    }

    private String toRemoteString() {
        if ((this == DEV || !(DataUtils.isProduction()))) {
            return "/datacenter/dhp";
        } else if (LOCAL_STRING.equalsIgnoreCase("/datacenter/dhp")) {
            return random.nextBoolean() ? "/datacenter/slp" : "/datacenter/ltr";
        } else if (LOCAL_STRING.equalsIgnoreCase("/datacenter/slp")) {
            return random.nextBoolean() ? "/datacenter/ltr" : "/datacenter/dhp";
        } else if (LOCAL_STRING.equalsIgnoreCase("/datacenter/ltr")) {
            return random.nextBoolean() ? "/datacenter/dhp" : "/datacenter/slp";
        }

        return null;
    }

    protected static String getLocalString(DataFlowEnum dataFlowType) {
        String prefix = dataFlowType.equals(DataFlowEnum.RECO) ? DataFlowEnum.RECO.value() : DataFlowEnum.UNRECO
                .value();
        return prefix + LOCAL_STRING;
    }

    protected static String getRemoteString(DataFlowEnum dataFlowType) {
        String prefix = dataFlowType.equals(DataFlowEnum.RECO) ? DataFlowEnum.RECO.value() : DataFlowEnum.UNRECO
                .value();
        return prefix + REMOTE_STRING;
    }

    private static final String LOCAL_STRING = ourlocation.toLocalString();
    private static final String REMOTE_STRING = ourlocation.toRemoteString();


    private static final String getHostNameOfServer() {
        try {
            return InetAddress.getLocalHost().getCanonicalHostName().toLowerCase();
        } catch (UnknownHostException ex) {
            // logging error
        }

        return null;
    }

    private static boolean isDevHost(String hostName) {
        return hostName.indexOf("." + DEV.name().toLowerCase() + ".") >= 0;
    }

    public static DatacenterEnum getCurrentDatacenter() {
        return ourlocation;
    }
}

And below is my DataFlowEnum which is also being used by other classes:

public enum DataFlowEnum {
    RECO("/tr"), UNRECO("/trpp");

    private String value;

    DataFlowEnum(String value) {
        this.value = value;
    }

    public String value() {
        return value;
    }
}

This is the way I am calling from my main application code to get local and remote strings:

String localPath = DatacenterEnum.getLocalString(key.getDataFlowType());
String remotePath = DatacenterEnum.getRemoteString(key.getDataFlowType());

I'd like to have this simplified or done better.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ I'm intrigued — what is the motivation for this fancy scheme? \$\endgroup\$ May 6, 2015 at 6:03
  • \$\begingroup\$ @200_success we had a chat on this long time back if you remember, we had a system in which user is sharded so that's why we are doing like this. We are using zookeeper to manager the shard mappings. \$\endgroup\$
    – david
    May 6, 2015 at 18:37
  • \$\begingroup\$ Follow-up question \$\endgroup\$ May 17, 2015 at 5:32

1 Answer 1

6
\$\begingroup\$

There are two basic issues I see with your code, and they are "big" issues:

  1. The code is not easy to extend... if you get a new datacenter, it will be a huge pain to reconfigure. What if your DEV environment gets two datacenters?
  2. Your access patterns are all .... bizarre, in that you have protected static methods (what does that mean?) and all the other access is through static methods as well.

I originally read your code, and thought it did things using a strategy I am familiar with, and it seemed to make sense, then I realized that the static methods and fields are all subtly different, and it all falls apart.

There's something 'funky' here.

I think the bottom line is.... why are you using an enum? Is that the right solution? When do you ever use the enum instances outside the enum? You use the String names and the name of the 'local' system, but never any of the other ones.... right? You use the 'remote' and 'local' directories, etc.

I think you should consider dropping the DatacenterEnum entirely, and just having a static class that loads itself up as a collection of 'config constants' that sort everything out at initialize time (reading from a config file for extensibility...?) That will save a lot of problems with maintainability in the future too.

All in all, I had this answer half-built where I was going to suggest a complciated system of static initialization, but, I think, the right solution would be a simple wrapper around a properties file, where you encode the data centers, which "paths" can be selected as remote paths, etc. and just encode the data that way.

Here is what I imagine a config file could look like:

dev_domain_part = dev
dev_local_datacenter = dhp
dev_remote_datacenter = dhp

datacenter_names = dhp slp ltr
datacenter_default = dhp
reco_prefix = /tr/datacenter/
reco_suffix = 
unreco_prefix = /trpp/datacenter/
unreco_suffix =

This is how I would read it:

import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Properties;
import java.util.Random;
import java.util.stream.Stream;

@SuppressWarnings("javadoc")
public class DatacenterDetail {

    private static final String keyCheckDev = "dev_domain_part";
    private static final String keyDevLocalDatacenter = "dev_local_datacenter";
    private static final String keyDevRemoteDatacenter = "dev_remote_datacenter";

    private static final String keyDatacenters = "datacenter_names";
    private static final String keyDefaultDatacenter = "datacenter_default";
    private static final String keyRecoPrefix = "reco_prefix";
    private static final String keyRecoSuffix = "reco_suffix";
    private static final String keyUnRecoPrefix = "unreco_prefix";
    private static final String keyUnRecoSuffix = "unreco_suffix";

    private static final String localHost;
    private static final String localDatacenter;
    private static final String localReco;
    private static final String localUnReco;
    private static final String remoteReco;
    private static final String remoteUnReco;

    static {
        Properties props = loadProperties();
        localHost = getHostNameOfServer();

        String devCheck = props.getProperty(keyCheckDev, ".dev.");
        String recoPrefix = props.getProperty(keyRecoPrefix, "/tr/datacenter/");
        String recoSuffix = props.getProperty(keyRecoSuffix, "");
        String unrecoPrefix = props.getProperty(keyUnRecoPrefix, "/trpp/datacenter/");
        String unrecoSuffix = props.getProperty(keyUnRecoSuffix, "");
        if (!DataUtils.isProduction() || localHost.indexOf("." + devCheck + ".") >= 0) {
            // we are running on a dev machine. Force the system in to dev mode.
            localDatacenter = props.getProperty(keyDevLocalDatacenter, "dhp");
            String remotedc = props.getProperty(keyDevRemoteDatacenter, "dhp");
            localReco = recoPrefix + localDatacenter + recoSuffix;
            localUnReco = unrecoPrefix + localDatacenter + unrecoSuffix;
            remoteReco = recoPrefix + remotedc + recoSuffix;
            remoteUnReco = unrecoPrefix + remotedc + unrecoSuffix;
        } else {
            String defaultdatacenter = props.getProperty(keyDefaultDatacenter, "dhp");
            String[] datacenters = props.getProperty(keyDatacenters, "dhp slp ltr").trim().split("\\s+");
            localDatacenter = getLocalDC(localHost, defaultdatacenter, datacenters);
            String remotedc = getRandomRemote(localDatacenter, datacenters);
            localReco = recoPrefix + localDatacenter + recoSuffix;
            remoteReco = recoPrefix + remotedc + recoSuffix;
            localUnReco = unrecoPrefix + localDatacenter + unrecoSuffix;
            remoteUnReco = unrecoPrefix + remotedc + unrecoSuffix;
        }

    }


    private static Properties loadProperties() {
        String cname = DatacenterDetail.class.getName();
        String propname = "/" + cname.replace('.', '/') + ".properties";
        Properties props = new Properties();
        try (InputStream is = DatacenterDetail.class.getResourceAsStream(propname)) {
            if (is == null) {
                throw new IllegalStateException("Cannot locate properties " + propname);
            }
            props.load(is);
        } catch (IOException e) {
            e.printStackTrace();
            throw new IllegalStateException("Unable to load proprties from system resource " + propname, e);
        }
        return props;
    }

    private static String getRandomRemote(String localdc, String[] datacenters) {
        String[] notlocal = Stream.of(datacenters).filter(dc -> !localdc.equals(dc)).toArray(s -> new String[s]);
        if (notlocal.length == 0) {
            return localdc;
        }
        if (notlocal.length == 1) {
            return notlocal[0];
        }
        Random rand = new Random();
        return notlocal[rand.nextInt(notlocal.length)];
    }

    private static String getLocalDC(String hostname, String defaultdatacenter, String[] datacenters) {
        for (String dc : datacenters) {
            if (hostname.indexOf("." + dc + ".") >= 0) {
                return dc;
            }
        }
        return defaultdatacenter;
    }

    private static final String getHostNameOfServer() {
        try {
            return InetAddress.getLocalHost().getCanonicalHostName().toLowerCase();
        } catch (UnknownHostException ex) {
            // logging error
        }

        return null;
    }


    public static String getLocalString(DataFlowEnum dataFlowType) {
        return dataFlowType.equals(DataFlowEnum.RECO) ? localReco : localUnReco;
    }

    public static String getRemoteString(DataFlowEnum dataFlowType) {
        return dataFlowType.equals(DataFlowEnum.RECO) ? remoteReco : remoteUnReco;
    }

    public static String getCurrentDatacenter() {
        return localDatacenter;
    }

    public static String getCurrentHostName() {
        return localHost;
    }

    public static void main(String[] args) {

        System.out.println(getCurrentDatacenter());
        System.out.println(getCurrentHostName());
        System.out.println(getLocalString(DataFlowEnum.RECO));
        System.out.println(getLocalString(DataFlowEnum.UNRECO));
        System.out.println(getRemoteString(DataFlowEnum.RECO));
        System.out.println(getRemoteString(DataFlowEnum.UNRECO));
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ Thanks for your suggestion. I though about this for a while and I came up with same solution with ENUM but this time it is generic, if any new datacenter gets added up, I just need to modify the ENUM by adding a new datacenter property. Your suggestion also looks good but in both the cases, I need to release a new version of client to our customers who are using it so I thought I can go with ENUM solution by making it generic. I have posted follow up question here. \$\endgroup\$
    – david
    May 16, 2015 at 17:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.