How can I represent key with list of values for ea

2019-03-03 04:48发布

I have two environment PROD and STAGING. In prod environment we have three datacenters ABC, DEF and PQR and staging has one datacenter CORP. Each datacenter has few machines and I have constant defined for them as shown below:

// NOTE: I can have more machines in each dc in future
public static final ImmutableList<String> ABC_SERVERS = ImmutableList.of("tcp://machineA:8081", "tcp://machineA:8082");
public static final ImmutableList<String> DEF_SERVERS = ImmutableList.of("tcp://machineB:8081", "tcp://machineB:8082");
public static final ImmutableList<String> PQR_SERVERS = ImmutableList.of("tcp://machineC:8081", "tcp://machineC:8082");

public static final ImmutableList<String> STAGING_SERVERS = ImmutableList.of("tcp://machineJ:8087","tcp://machineJ:8088");

Now I have another constant defined in the same class which groups by DC to List of machines for each environment type.

public static final ImmutableMap<Datacenter, ImmutableList<String>> PROD_SERVERS_BY_DC =
  ImmutableMap.<Datacenter, ImmutableList<String>>builder()
      .put(Datacenter.ABC, ABC_SERVERS).put(Datacenter.DEF, DEF_SERVERS)
      .put(Datacenter.PQR, PQR_SERVERS).build();

public static final ImmutableMap<Datacenter, ImmutableList<String>> STAGING_SERVERS_BY_DC =
  ImmutableMap.<Datacenter, ImmutableList<String>>builder()
      .put(Datacenter.CORP, STAGING_SERVERS).build();

Now in some other class, basis on what environment I am in (Utils.isProd()), I get either PROD_SERVERS_BY_DC or STAGING_SERVERS_BY_DC.

Map<Datacenter, ImmutableList<String>> machinesByDC = Utils.isProd() ? Utils.PROD_SERVERS_BY_DC : Utils.STAGING_SERVERS_BY_DC;

Now I think this can be represented in much better way in some sort of Enum instead of having constants defined like I have above but I am not able to figure out how can I do that? I started off with this but got confuse on how can I have single key for each DC and then multiple values as List of machines for that DC and then I need to group them by environment as well.

// got confuse on how can I make key (DC) and list of values (machines) for each environment type.
public enum DCToMachines {
  abc(tcp://machineA:8081", "tcp://machineA:8082"), 

  private final List<String> machines;
  private final Datacenter datacenter;
  ...


}

2条回答
老娘就宠你
2楼-- · 2019-03-03 05:12

While this is a bit tricky without knowing full implementation I might be able to get you started.

Enums are interesting in that they are Immutable which solves the declaring constant fields and variables. Here is my attempt to solve what you are looking for.

public enum Server {
    // Constant style naming because Enums are Immutable objects
    ABC_SERVERS("tcp://machineA:8081", "tcp://machineA:8082"),
    DEF_SERVERS("tcp://machineB:8081", "tcp://machineB:8082"),
    PQR_SERVERS("tcp://machineC:8081", "tcp://machineC:8082"),
    CORP("tcp://machineZ:8081");

    /* After the name declaration you can essentially do normal class 
       development
    */ Fields
    private List<String> servers = new ArrayList<>();
    public boolean isProd; // you could use something as simple as a 
    //boolean to determine the environment

    // Enums use a private constructor because they are never 
    //instantiated outside of creation of this Enum class
    private Server(String... dataCenter){

    // because of the varargs parameter there is a potential for 
    //multiple Strings to be passed
        for (String tcp :
                dataCenter) {
            this.servers.add(tcp);
        }

        // You can access the name property of the Enum that is being created
        if (this.name() == "CORP")
            this.isProd = false;
        else
            this.isProd = true;

    }

    // You could make the List public but this demonstrates some encapsulation
    public List<String> getServers() {
        return servers;
    }
}

I then checked that each "machine" was being added in another class

    // these are what I used to check to make sure that each string is being "mapped" correctly
    List<String> prodServer = new ArrayList<>();
    List<String> stagingServer = new ArrayList<>();

    for (Server server :
            Server.values()) {
        if (server.isProd) {
            for (String machine :
                    server.getServers()) {
                prodServer.add(machine);
            }
        }
        else {
            for (String machine:
                    server.getServers()) {
                stagingServer.add(machine);
            }
        }
    }

    System.out.println(prodServer);

    System.out.println(stagingServer);

Also note that to get the actual enum names you use the .values() function of the Enum class.

查看更多
姐就是有狂的资本
3楼-- · 2019-03-03 05:19

I started off with this but got confuse on how can I have single key for each DC and then multiple values as List of machines for that DC and then I need to group them by environment as well.

You forgot an important just : enum may store members (method and fields) specific to each enum value but it may also store static members if required.
What you need is moving the maps that groups by environment directly in the enum class :

private static final ImmutableMap<Datacenter, ImmutableList<String>> PROD_SERVERS_BY_DC;
private static final ImmutableMap<Datacenter, ImmutableList<String>> STAGING_SERVERS_BY_DC;

You could use an inner map to return the expected map according to the user request :

private static final Map<Env, ImmutableMap<Datacenter, ImmutableList<String>>> SERVERS_BY_ENV;

Where Env is another enum :

public static enum Env {
    PROD, STAGING
}

In the code that I will present I added as an enclosing type of Datacenter but it would be probably better in its own file as the environment is probably not a concept used exclusively by datacenters.

At last, the way you are using to retrieve servers by environment splits related responsibilities in 2 classes (Datacenter enum and the current class) :

Map<Datacenter, ImmutableList<String>> machinesByDC = Utils.isProd() ? Utils.PROD_SERVERS_BY_DC : Utils.STAGING_SERVERS_BY_DC;

introducing in the enum a method to do this operation would be nicer :

public static ImmutableMap<Datacenter, ImmutableList<String>> getServers(Env env){...}

Which would increase the cohesion of the enum class.

Here is the enum updated :

import java.util.HashMap;
import java.util.Map;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;

public enum Datacenter {

   ABC(Env.PROD, "tcp://machineA:8081", "tcp://machineA:8082"), 
   DEF(Env.PROD, "tcp://machineB:8081", "tcp://machineB:8082"), 
   PQR(Env.PROD, "tcp://machineA:8081", "tcp://machineA:8082"), 
   CORP(Env.STAGING, "tcp://machineC:8081", "tcp://machineC:8082");

    public static enum Env {
        PROD, STAGING
    }

    private Env env;
    private String[] url;

    private static final ImmutableMap<Datacenter, ImmutableList<String>> PROD_SERVERS_BY_DC;
    private static final ImmutableMap<Datacenter, ImmutableList<String>> STAGING_SERVERS_BY_DC;
    private static final Map<Env, ImmutableMap<Datacenter, ImmutableList<String>>> SERVERS_BY_ENV;

    static {

        Builder<Datacenter, ImmutableList<String>> prodDataCenterBuilder = ImmutableMap.<Datacenter, ImmutableList<String>>builder();
        Builder<Datacenter, ImmutableList<String>> stagingDataCenterBuilder = ImmutableMap.<Datacenter, ImmutableList<String>>builder();

        for (Datacenter datacenter : Datacenter.values()) {
            if (datacenter.env == Env.PROD) {
                prodDataCenterBuilder.put(datacenter, ImmutableList.of(datacenter.url));
            } else if (datacenter.env == Env.STAGING) {
                stagingDataCenterBuilder.put(datacenter, ImmutableList.of(datacenter.url));
            }

            else {
                throw new IllegalArgumentException("not exepected env " + datacenter.env);
            }
        }

        PROD_SERVERS_BY_DC = prodDataCenterBuilder.build();
        STAGING_SERVERS_BY_DC = stagingDataCenterBuilder.build();

        SERVERS_BY_ENV = new HashMap<>();
        SERVERS_BY_ENV.put(Env.PROD, PROD_SERVERS_BY_DC);
        SERVERS_BY_ENV.put(Env.STAGING, STAGING_SERVERS_BY_DC);

    }

    Datacenter(Env env, String... url) {
        this.env = env;
        this.url = url;
    }    

    public static ImmutableMap<Datacenter, ImmutableList<String>> getServers(Env env) {
        ImmutableMap<Datacenter, ImmutableList<String>> map = SERVERS_BY_ENV.get(env);
        if (map == null) {
            throw new IllegalArgumentException("not exepected env " + env);
        }
        return map;
    }

}

And here how to use it :

public static void main(String[] args) {        
    ImmutableMap<Datacenter, ImmutableList<String>> servers = Datacenter.getServers(Env.PROD);
    servers.forEach((k, v) -> System.out.println("prod datacenter=" + k + ", urls=" + v));

    servers = Datacenter.getServers(Env.STAGING);
    servers.forEach((k, v) -> System.out.println("staging datacenter=" + k + ", urls=" + v));
}

Output :

prod datacenter=ABC, urls=[tcp://machineA:8081, tcp://machineA:8082]

prod datacenter=DEF, urls=[tcp://machineB:8081, tcp://machineB:8082]

prod datacenter=PQR, urls=[tcp://machineA:8081, tcp://machineA:8082]

staging datacenter=CORP, urls=[tcp://machineC:8081, tcp://machineC:8082]

查看更多
登录 后发表回答