Ok, ihr ratet mir also eher von Microservices ab, und empfehlt eher den Schritt zu gehen, das aktuelle Projekt zu optimieren, so dass weitere Features und Services ohne weiteres implementiert werden können?
Klar, es gibt nicht DIE Architektur, die auf alles passt, hätte nur gedacht, dass Microservices "alles etwas einfacher machen", aber es scheint eher das Gegenteil zu sein.
Dann auf jeden Fall vielen Dank für das Feedback!
Servus zusammen,
ich arbeite bei einem mittelstätigen Unternehmen, welches im Tankstellenbereicht tätig ist.
Eins unserer Projekte hat die Aufgabe bei verschiedenen Tankautomaten-Herstellern Konfigurationen der Tankautomaten abzurufen, oder diese zu setzen. Jeder Hersteller nutzt da logischweise einen anderen Kommunikationsansatz. Die einen eine Rest API, die anderen eine TCP Verbindung.
Da sich dieeses Projekt langsam zu einem alles könnenden Monolithen entwickelt und in nächster Zeit nun weitere Funktionen hinzu kommen werden, würde ich alles etwas umstrukturieren wollen und den Ansatz der Microservice-Architektur umsetzen. Beispielsweise ein Service kümmert sich um die ankommenden Konfigurationen und ein weiterer Service sendet neue Konfigurationen. Nennen wir sie mal "Bekomme-Konfiguration-Service" und "Sende-Konfiguration-Service". Beide Services würden aber weiterhin auf dem gleichen Server laufen.
Mein Stolperstein ist hier allerdings die TCP Geschichte. Unser aktueller Service dient als TCP Server, die Hersteller bauen also eine Verbindung zu uns auf und nur durch diese Verbindung kann ich neue Konfigurationen senden.
Wenn ich nun aber den TCP Server abkoppel und in den "Bekomme-Konfiguration-Service" stecke, wie komme ich dann im "Sende-Konfiguration-Service" an die Verbindung? Oder macht es da eher Sinn den TCP Server als dritten Service anzulegen. Die ankommenden Konfigurationen würden dann an den "Bekomme-Konfigurationen-Service" weiter geleitet werden und neue Konfigurationen vom "Sende-Konfigurationen_Service" werden dann zum TCP Service geleitet und dort gesendet. Aber, wie schon gefragt, wie komme ich von aussen an die richtige Verbindung?
Oder macht hier ein ganz anderer Ansatz mehr Sinn?
Servus zusammen,
wie ich ja mittlerweile schon gelernt habe, werden Credentials aus Sicherheitsgründen in den Umgebungsvariablen hinterlegt und im Code ausgelesen.
Wie verhält es sich aber mit Zertifikaten, wie ist da der "way to go"?
Im Moment installiere ich die Zertifikate händisch im Certificate Store, da ich keine CI/CD Pipeline nutze. Das Auslesen der Zertifikate mache ich dann mittels
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2 Certi = store.Certificates.First(x => x.FriendlyName == "meinZertifikat");
Danke schonmal!
Kriz
Zitat von Abt
Erster Grundregel: Deine ASP.NET Controller / Actions sind Deine Schnittstelle nach Außen.
Du solltest also eigene Modelle nur für diese Außenwelt bereitstellen: API Modelle. Deine Entitäten oder "Business Models" haben in dieser Welt nichts zu suchen.
Mit API Modelle meinst Du Models, die nur als Rückgabe für die Actions dienen? Also beispielsweise:
public class UpdateResult { public bool Success { get; set; } public DateTime ExecutionTime { get; set; } }
auf die Delete-Action? Klar, ein entsprechender ResponseCode tuts auch, aber nur verständnisshalber...
Code wie
IActionResult result = personService.UpdatePerson(person);
zeigt, dass die Logik falsch aufgebaut ist.
Der obige Code ist "zusammen gezimmerter Beispielcode", und kommt so in meinem Projekt nicht vor. Ja, da häte ich etwas genauer sein können. Methoden wie **personService.UpdatePerson(person)**
geben dann eher einen bool, oder integer zurück. Das würde dann doch eher wieder ins Schema passen, oder?
Zweiter wichtiger Punkt: jeder Request ist isoliert. Du solltest nur die Instanzen zwischen Requests (=Threads) teilen, die dafür konzipiert sind.
Deine Repositories sind das nicht, und daher Deine Logikbausteine ebenfalls nicht. Das muss also alles als Transient registriert werden - statischen Instance Sharing wiePersonRepository.Instance
ist ein No-Go.
In einem anderen Thread(finde ihn gerade nicht) wurde mir nahe gelegt Datenbanken als Singleton anzulegen. Was ist hier denn in dieser Situation anders, dass man es nicht als Singleton machen sollte?
Lass sowas wie "PersonService". Das ist das, was innerhalb von kürzester Zeit in riesen, unflexiblen Code-Klassen endet.
Arbeite mit Use Cases wie eben den Handlern, die Du unten angedeutet hast. Du kannst dafür fertige Frameworks (Wie MediatR) nehmen, die Dir paar Sachen zusätzlich geben, oder alles selbst machen und simpel halten.
Mein Gedankengang war, dass ich so wenig Logik-Code wie möglich in meinen Controllern habe. Also erstelle ich einen PersonService, an den werden die Requests weiter gegeben und dieser ruft dann die entsprechenden Handler auf. Order macht man da (je nach Komplexität) pro Action einen eigenen Handler der dann die Request übergeben bekommt?
Doch, das funktioniert genau so. Nur reicht man es nicht durch, sondern das macht alles das DI Framework automatisch. Du erstellst es halt von Hand, was nicht Sinn der Sache ist. Aber der Flow ist:
- Action nutzt Handler
- Handler nutzt Repository
Mit durchreichen meine ich: Wenn ich im Handler das Repository nutzen möchte, dann injiziere ich es dort im Konstruktor. Wenn ich nun den Handler im Controller nutzen möchte, würde ich im Controller das Repository injizieren und es an den Handler weiter geben. Oder würde der Handler bereits im Controller injiziert werden?
Was in Deinem Code nicht passt ist, dass Deine
UpdatePerson
Action Logik beinhaltet.
Sie prüft welcher Handler ausgeführt wird: das ist ebenfalls Logik.
Ne, das passiert in diesem Beispiel im PersonService, nicht im Controller.
Servus,
ich habe vor einigen Jahren als Quereinsteiger in der Softwareentwicklung angefangen. Dem entsprechend sehe viele meiner älteren Projekte aus, viel gewuchert und kaum sortiert. Nun bin ich beim Refactoring meines ersten Projekts (WebAPI) und mir haben sich (bis jetzt) zwei Fragen ergeben.
Meine Controller sehen beispielsweise so aus:
public class PersonController : Controller
{
private readonly PersonService personService;
public PersonController(IPersonService _personService)
{
personService = _personService;
}
public IActionResult UpdatePerson(Person person)
{
IActionResult result = personService.UpdatePerson(person);
return DeletedResult(result);
}
public IActionResult DeletePerson(int id)
{
bool result = personService.DeletePerson(id);
return result;
}
}
Jeder Controller bekommt per DI einen entsprechenden Service in den Konstruktor. Die Services bekommen ein Repository und de Repository bekommen Credentials, aller per DI. So weit, so klar.
Meine ServiceImplementierung sieht beispielsweise so aus:
public class PersonService : IPersonService
{
private readonly IPersonRepository personRepository;
public PersonService(IPersonRepository _personRepository)
{
personRepository = _personRepository;
}
public bool DeletePerson(int id)
{
int result = personRepository.DeletePerson(id);
return (result == 1);
}
}
Nun möchte ich aber beispielsweise in meiner PersonService.Update(Person)
unterschiedliche Handler/Klassen nutzen, um die Person weiter zu bearbeiten:
public IActionResult UpdatePerson(Person person)
{
IActionResult result;
MaleHandler maleHandler = new MaleHandler();
FemaleHandler femaleHandler = new FemaleHandler();
if (person.Gender == Gender.Male)
{
result = maleHandler.Update(person);
}
else
{
result = femaleHandler.Update(person);
}
return result;
}
Wenn ich nun in meinen Handlern (FemaleHandler
bzw MaleHandler
) auf die Datenbankschicht zugreifen möchte, müsste ich ja hier im Kontruktor das bereits erstellte personRepository übergeben:
MaleHandler maleHandler = new MaleHandler(personRepository);
Das wirkt mir aber irgendwie "nicht richtig", das Repository weiter durch zu reichen, "bis man es braucht".
Dieses funktioniert leider nicht:
public class MaleHandler
{
private readonly IPersonRepository personRepository;
public MaleHandler()
{
personRepository = PersonRepository.Instance;
}
public void Update(Person person)
{
...
}
}
Da ich im PersonRepository-Konstruktor die Credentials injiziere.
Gibt es da einen anderen Ansatz?
Ich sehe immer wieder, dass die verschiedenen Schichten in verschiedenen Projekten innerhalb einer Solution abgebildet sind. Was genau bringt dieses Vorgehen? Aktuell bilde ich die verschiedenen Schichten per Ordner ab und sehe in dem Mehraufwand der einzelnen Projekte keinen Mehrwert.
Danke schonmal!
Kriz
Wenn ich versuche meinen Avatar zu ändern bekomme ich nach Bildauswahl und Click auf 'Änderung speichern' die Fehlermeldung
Es wurde kein Bild angegeben.
The value '' is invalid.
Das benutzte Bild ist anbei.
Kriz
Klasse, vielen Dank! Das hat mir alles schonmal gut weiter geholfen!
Wir sind eine 2-Mann Firma und ich bin der einzige Entwickler, da war es bist jetzt noch nicht so nötig CI/CD und unterschiedliche Umgebungen "professionell" aufzuziehen, aber am Ende des Tages ist es einfach nur sinnvoll es von Anfang an auch schon richtig anzugehen.
Ich werde mal versuchen mit GitHub Actions einen CI/CD Workflow zu erstellen, das scheint einer der wenigen Anbieter zu sein, die nicht sooo teuer sind.
Vielen Dank!
Kriz
Zitat von Abt
Das Problem aber an für sich existiert gar nicht, weil Du auf einem Server individuelle Einstellungen über die Umgebungsvariablen umsetzen solltest (zB Connection Strings) und nicht über die config.
ConnectionsString in den Umgebungsvariablen hinterlegen? Hätte gedacht die sind eher für systemrelevante Sachen relevant... und nicht für ConnectionStrings einer Anwendung.
Die Lösung war übrigens der Publishdatei das Enviroment noch mit zu übergeben, das fehlte. Nun arbeitet es wie gewünscht. Danke nochmal an Th69 und T-Virus, manchmal sind es die offensichtlichen Sachen.
@Abt: Wenn nun aber die appconfig nicht für ConnectionStrings und Api-Pfade genutzt werden "soll", muss ich nun jedes mal beim Veröffentlichen einer Anwendung ein Batch laufen lassen, damit die nötigen Strings und Pfade in den Umgebungsvariablen eingetragen werden? Das wirkt mir etwas umständlich?!
Servus zusammen,
ich versuche ein ASP.Net Core Projekt so umzustellen, dass DB-Connectionstrings und API-Pfade aus der appconfig.json gelesen . Abhähig von der gewählten Konfiguration soll die appconfig.Development.json oder appconfig.Testserver.json (und noch ein paar weitere Konfiguratonen) gewählt werden.
Ich habe in der launchsettings.json mehrere Profile angelegt, die verschiedene Konfiguratonen "bedienen", beispielsweise:
"Development": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": "true",
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
Wenn ich nun lokal das Projekt mit "Development" starte, wird die korrekte appconfig gewählt. Soweit alles schön.
Nun veröffentliche ich mein Projekt per WebDeploy auf meinen IIS-Server, in den Einstellungen habe ich unter Configuration "Development" angegeben. Es wird allerdings immer die appsettings.json genutzt.
Mein HostBuilder:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddEnvironmentVariables();
var env = hostingContext.HostingEnvironment;
config.AddJsonFile($"appsettings.json", optional: false, reloadOnChange: true);
config.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
}
);
Wenn ich env.EnviromentName
auslese, ist es immer Production, klar dass dann die "falsche" appsettings.json genutzt wird.
Ich habe auf dem Server die Umgebungsvariablen ASPNETCORE_ENVIROMENT
und DOTNET_ENVIROMENT
(Hinweis auf GitHub) auf "Development" gesetzt, das hat allerdings nichts geändert.
Wo ist mein Fehler?
Danke schonmal!
Kriz
Dies ist in diesem Fall auch so, alle Kommunikation geht über einen API Server und dann erst weiter zur DB.
Mir ging es darum, dass ich verschiedene APIs für verschiedene UseCases habe (Releease, Debug, Debug mit Live Daten, usw). Anhand der ausgewählen Build Konfiguration nutze ich dann die entsprechende API. Meine Frage war nun ob es in diesem Fall einen einfachreren/besseren Weg gibt.