Strukturierung API Klasse

Hallo zusammen, brauch nochmal eure Hilfe.

Ich habe nun eine Klasse erstellt, mit der ich eine API anspreche, jedoch ist der Code sehr aufgebläht und teilweise redundant.

Wie würdet Ihr die folgende klasse erstellen bzw umstrukturieren? Es kommen noch weitere Methoden hinzu welche Artikel updaten etc.

public class ShopwareProductService {
	

	/**
	 * Get the product from specified SKU in parameter
	 * 
	 * @param domain
	 * @param apiUser
	 * @param apiPassword
	 * @param productSKU
	 */
	public static void getProduct(String domain, String apiUser, String apiPassword, String productSKU) {
		ResteasyClient client = new ResteasyClientBuilder().build();
		ResteasyWebTarget webTarget = client.target(domain + "/api/articles/" + productSKU + "?useNumberAsId=true");
		webTarget.register(new BasicAuthentication(apiUser, apiPassword));
		Response response = webTarget.request().accept("application/json").get();
		response.bufferEntity(); // Response nicht schließen

		ShopwareProduct sProduct = response.readEntity(ShopwareProduct.class);
		 System.out.println("id " + sProduct.getData().getName());
	}

	/**
	 * Generate a new product and send it via API
	 * 
	 * @param domain
	 * @param apiUser
	 * @param apiPassword
	 */
	public static void createProduct(String domain, String apiUser, String apiPassword) {
		ResteasyClient client = new ResteasyClientBuilder().build();
		ResteasyWebTarget webTarget = client.target(domain + "/api/articles/");
		webTarget.register(new BasicAuthentication(apiUser, apiPassword));
		
		ShopwareProduct product = new ShopwareProduct();
		product.setName("Blabla");
		product.setTax("19");
		product.setSupplier("Nike");
		product.setActive(true);

		/*
		 * Prices
		 */
		List<Prices> priceList = new ArrayList<>();
		Prices prices = new Prices();
		prices.setCustomerGroupKey("EK");
		prices.setPrice(99.99);
		priceList.add(prices);

		MainDetail mainDetail = new MainDetail();
		mainDetail.setNumber("SKUJJJ");
		mainDetail.setEan("123235423423");
		mainDetail.setSupplierNumber("q6511x");
		mainDetail.setActive(true);
		mainDetail.setPrices(priceList);
		product.setMainDetail(mainDetail);

		
		/*
		 * Mapping only for debugging purposes, so the product could be printed as a
		 * json string
		 */
		ObjectMapper oMapper = new ObjectMapper();
		try {
			System.out.println(oMapper.writeValueAsString(product));
		} catch (IOException e) {
			e.printStackTrace();
		}
		Response response = webTarget.request().post(Entity.entity(product, "application/json"));
		System.out.println(response.readEntity(String.class));
	}

}

Diese drei Zeilen machst du z.B. in beiden Methoden und können daher in eine private Hilfsfunktion ausgelagert werden, der du nur die URL als String-Parameter übergibst.

Weiters vl keine statischen Funktionen, sondern ein Object erzeugen bei dem du am Konstruktor username, passwort und domain einmal angibst und dann bei den Methoden nicht mehr.

1 „Gefällt mir“

Danke, das erstellen des Webtargets, würde ich sogar in eine Klasse haben wollen (static?)

Ich werde noch weitere Klasse erstellen in welchen dann die Kategorien, Bestellungen etc verarbeitet werden, diese brauchen ja auch alle eine Verbindung zur API, da bietet sich eine Klasse „APiConnection“ an oder? In dieser dann eine Methode static getWebtarget(domain, apiName, apiPasswort)?

Schau dir mal die JDBC-API an, die dürfte recht aufschlußreich für dich sein.

ICh dachte eher an so etwas (Achtung nur pseudo code)

public ShopwareProductServiceHelper {
private String domain;
private String user;
private String pw;

public ShopwareProductServiceHelper (domain, user, pw) {
...
} 
private ResteasyWebTarget buildWebTarget(String path) {
		ResteasyClient client = new ResteasyClientBuilder().build();
		ResteasyWebTarget webTarget = client.target(domain + "/api/articles/");
		webTarget.register(new BasicAuthentication(user, password));
return webTarget;
}
public <T> T get(String path, Class<T> responseClass) {
  var webTarget = buildWebTarget(path)
  T result = response.readEntity(responseClass);
  return result;
}

public <T,R> R post(String path, T bodyContent, Class<R> responseClass) {
  var webTarget = buildWebTarget(path)
  Response response = webTarget.request().post(Entity.entity(bodyContent, "application/json"));
  R result = response.readEntity(responseClass);
  return result;
}


public class ShopwareProductService {
  private ShopwareProductServiceHelper communiction = .....;

  public getProduct(String productSKU) {
     communiction.get("/api/articles/" + productSKU + "?useNumberAsId=true",ShopwareProduct.class);
  }
  public createProduct(... any parameter you need to create a product) { 
     ShopwareProduct product = new ShopwareProduct();
		product.setName("Blabla");
		product.setTax("19");
		product.setSupplier("Nike");
		product.setActive(true);

		/*
		 * Prices
		 */
		List<Prices> priceList = new ArrayList<>();
		Prices prices = new Prices();
		prices.setCustomerGroupKey("EK");
		prices.setPrice(99.99);
		priceList.add(prices);

		MainDetail mainDetail = new MainDetail();
		mainDetail.setNumber("SKUJJJ");
		mainDetail.setEan("123235423423");
		mainDetail.setSupplierNumber("q6511x");
		mainDetail.setActive(true);
		mainDetail.setPrices(priceList);
		product.setMainDetail(mainDetail);
      communication.post(/api/articles/,product,String.class);
  }


}

aber auch hier würde ich auf jegliches static verzichten. Da ich Resteasy nicht kenne weis ich natürlich nicht welche einschränkungen die Generischen Parameter R und T haben müssten. Also z.B

Hey das sieht prima aus! So in etwa hab ich es jetzt fast, werde mal noch ein wenig basteln. Vielen Dank :slight_smile:

Nur was hat es hiermit auf sich?

Das ist eine generische Methode mit zwei generischen Parametern. T steht in dem Bsp. oben für deinen Body und R für Response

http://openbook.rheinwerk-verlag.de/javainsel/javainsel_09_001.html#dodtp710f88eb-3589-44a7-9919-b2780446afc0

näheres bezüglich generische Parameter findest du in dem Link

der Vorteil von diesem generischen Zeug ist, dass du get und post für verschiedene Object-Klassen wieder verwenden kannst und nicht jedes mal eine neue post-Methode schreiben musst.

Die Verwendung siehst du eh in der createProduct-Methode oben

1 „Gefällt mir“

schau ich mir an, hab ich noch nie benutzt danke

Kapselt der ResteasyClientBuilder nicht eigentlich schon alles? Der ist doch sogar fluent?

public void getProduct(String domain, String apiUser, String apiPassword, String productSKU) {
    Response response = new ResteasyClientBuilder().build()
        .target(domain + "/api/articles/" + productSKU + "?useNumberAsId=true")
        .register(new BasicAuthentication(apiUser, apiPassword))
        .request()
        .accept("application/json")
        .get();
    response.bufferEntity(); // Response nicht schließen

    ShopwareProduct sProduct = response.readEntity(ShopwareProduct.class);
    System.out.println("id " + sProduct.getData().getName());
}

Ich finde das auf jeden Fall lesbar. Eventuell würde ich einzelne Teile in private Klassen auslagern. Wenn eine Klasse allerdings Helper im Namen trägt, klingt das für mich schon “nicht optimal”. Nach IOSP könnte man z.B. die Target-Url bzw sogar den Aufbau des Requests in eine Methode kapseln.

Wie du aber in deinem Bsp siehst, hast du jedesmal ziemlcih viel Code der in jedem aufruf gleich ist. z.B.
new ResteasyClientBuilder().build()
register(BasicAuth)
target(domain nur der pfad dahinter ändert sich

deswegen wollte ich ihm vorschlagen, dies noch einmal wegzukappseln

Ja der Name ist definitiv unglücklich gewählt, weil dern Name vl doch eher „ShopwareProductServiceCommunication“ sein sollte.

aber das ist nur meine persönliche Meinung.

mir ging es auch darum, die sich wiederholenden zeilen auszulagern, wobei es jetzt noch übersichtlich ist die 3 Zeilen da.

Wie würdest du das aufbauen @anon2594669? was meinst du mit in private Klassen auslager, welche Teile?

Wahrscheinlich ähnlich wie @AmunRa in seiner Helper-Klasse nur als private Methoden. Ich würde das, was accept() zurückgibt in der Methode zurückgeben.

Soweit so gut, danke euch beiden! Nun ist es etwas lesbarer, aber so ganz auf dem Holzweg war ich nicht oder? So ein Design erarbeitet man vielleicht auch öfter mal im Team oder? (Wenn man ein Team hat ;P)

Wie kann ich denn die Rückgabewerte der generischen Methode
connection.post(“api/articles/”, product, ShopwareProduct.class);
verarbeiten?

String result = connection.post(“api/articles/”, product, String.class);

1 „Gefällt mir“

:man_facepalming:

Eins noch, seit ich die genersischen Methoden nutze, bekomme ich folgende Meldung
Can not deserialize instance of product.Data out of START_ARRAY token

und zwar hier

public <T> T get(String path, Class<T> responseClass) {
		ResteasyWebTarget webTarget = buildWebTarget(path);
		Response response = webTarget.request().get();
		//System.out.println(response.readEntity(String.class)); //Ausgabe ist korrekt
		T result = response.readEntity(responseClass); //HIER EXCEPTION
		return result;
	}

Ich rufe die Methode falsch aus, dachte ich muss Objekt.class angeben, welches ich erwarte

String shopwareProduct=	connection.get("/api/articles/" + productSKU + "?useNumberAsId=true", String.class);

funktioniert

Dachte ich muss

	ShopwareProduct shopwareProduct=	connection.get("/api/articles/" + productSKU + "?useNumberAsId=true", ShopwareProduct.class);

angeben?

Habe mit RestEasy in letzter Zeit wenig gemacht, aber dein Modell muss zu der Antwort passen. Schau dir doch einfach mal den String an, ob er strukturell passt.

Generell ist ein String mit selbst parsen allerdings häufig stabiler, was externe APIs angeht.