Pomoc s strukturou aplikace

pospi
Člen | 18
+
0
-

Ahoj predem dekuji vsem za pomoct , muj dotaz je tento :
Budu mit aplikaci s pripojenim do vice databazi, napr. basic, user, forum(tzn. v jednom preseteru muze byt vice modelu z vice databazi), jakym zpusobem mam udelat strukturu aplikace? Moje predstava je tato :
V database.neon budu mit konfigurace databazi

database:
  basic:
    dsn: 'xxx'
    user: ''
    password: ''
    autowired: true
  forum:
    dsn: 'xxx'
    user: ''
    password: ''
    autowired: false
  user:
    dsn: 'xxx'
    user: ''
    password: ''
    autowired: false

dal bych chtel udelat 3 abstraktni tridy ktere budou prijimat db context, jake services(basic tam neni protoze je autowired)

services:
	- App\Models\ForumFacade(@database.forum.context)
	- App\Models\UserFacade(@database.user.context)

Z techto abstraktnich trid budou dedit jine tridy ktere budou s timto kontextem a popripade jinyma funkcema pracovat, tedy:

<?php
namespace App\Models;

use Nette;
use Nette\Database\Context;

abstract class BasicContext {

  /** @var Context */
  protected $database;

  public function __construct(Context $dbContext) {
    $this->database = $dbContext;
	}
}
<?php
namespace App\Models;

use Nette;
use Nette\Database\Context;

final class TestFacade extend App\Models\BasicContext
{
	public function getCompanies()
	{
		return $this->database
			->table('companies');
	}
}

V tuto chvily mi ale tuto predstavu komplikuje hlaska : „Nette\DI\ServiceCreationException Service of type App\Models\BasicContext: Class App\Models\BasicContext is abstract.“
Tedy premyslim jak to udelat jinak. dekuji za odpovedi

m.brecher
Generous Backer | 765
+
-1
-

Ahoj,

není dobré, aby abstraktní předek modelových tříd byl současně sám finální modelovou třídou, protože jak hláška napověděla, nelze z něj vytvořit instanci – je abstraktní. Udělej to třeba takto:

<?php

declare(strict_types=1);  // používej striktní typování !!!

namespace App\Models;

use Nette;
use Nette\Database\Explorer;   // používej moderní Explorer

abstract class AbstractModel
{
    public function __construct(protected Explorer $database) // moderní syntaxe php 8.0
    {}
}

a z AbstractModelu podědíš všechny tři finální modelové třídy BasicModel, ForumModel, UserModel:

<?php

declare(strict_types=1);

namespace App\Models;

use Nette;
use Nette\Database\Table\Selection;


final class BasicModel extends AbstractModel  // místo zastaralého <*>Facade používej raději <*>Model
{
	public function getCompanies(): Selection
	{
		return $this->database
			->table('companies');
	}
}

konfigurace:

https://doc.nette.org/…onfiguration#…

„Autowiring je zapnutý jen u služeb z první sekce.“ proto není autowired potřeba vůbec uvádět.

database:
  basic:
    dsn: 'xxx'
    user: ''
    password: ''

  forum:
    dsn: 'xxx'
    user: ''
    password: ''

  user:
    dsn: 'xxx'
    user: ''
    password: ''

a modelové třídy:

services:
	- App\Models\BasicModel		# zde je autowiring automaticky zapnutý
	- App\Models\ForumModel(@database.forum.explorer)
	- App\Models\UserModel(@database.user.explorer)

Kód je za předpokladu PHP 8.0 a nějaké novější verze Nette s Explorerem. Používej důsledně declare(strict_types=1);

Editoval m.brecher (25. 2. 21:32)

pospi
Člen | 18
+
0
-

Diky za odpoved urcite me to posunulo o kus dal(nejen v tom na co jsme se ptal)
Mam jeste dotaz:
Pokud bych se pozdeji rozhodl ze tridy BasicModel, ForumModel a UserModel nebudou finalni a budu dale z nich dedit.
A zaroven bych chtel mit v service pouze tento zapis :

services:
	- App\Models\BasicModel		# zde je autowiring automaticky zapnutý
	- App\Models\ForumModel(@database.forum.explorer)
	- App\Models\UserModel(@database.user.explorer)

Napr. Nova trida Basic1Model by dedila od BasicModel ale uz bych nechtel do service davat radek
- App\Models\Basic1Model
proste aby se mi ten Database\Explorer predal zkrz to dedeni. Je to mozne nebo musim kazdy model zapisovat do services?

Kamil Valenta
Člen | 762
+
+2
-

m.brecher napsal(a):

abstract class AbstractModel
{
    public function __construct(protected Explorer $database) // moderní syntaxe php 8.0
    {}
}

K čemu by takový abstraktní model měl být dobrý?

// místo zastaralého <>Facade používej raději <>Model

Čím je Facade zastaralé? Proč je srovnávána s modelem?
Model a fasáda má každý jinou funkci, nijak se nealternují, jeden není modernějším nástupcem druhého…

pospi napsal(a):
Napr. Nova trida Basic1Model by dedila od BasicModel ale uz bych nechtel do service davat radek

  • App\Models\Basic1Model

proste aby se mi ten Database\Explorer predal zkrz to dedeni. Je to mozne nebo musim kazdy model zapisovat do services?

Snažíš se „zabít“ smysl a výhody DI. Výsledkem bude netestovatelný, hůře čitelný a udržitelný kód.

Jedna možnost je každé modelové třídě předhodit DB konexi v NEONu. Chápu ale, že těch modelových tříd budou mraky a budou rozděleny do 3 skupin, podle DB… Je to celkem neobvyklé, ale asi bych i tak vše registroval s uvedenou DB. Pokud bys hodně nechtěl, šel bych cestou DB provideru, který se bude hlásit k závislosti na všech 3 DB a podle nějaké vnitřní logiky (třeba namespace modelové třídy) poskytne správnou DB.

m.brecher
Generous Backer | 765
+
0
-

@KamilValenta

K čemu by takový abstraktní model měl být dobrý?

Navazoval jsem na zadaný dotaz, kde byla chyba v tom, že abstraktní předek byl současně finálním modelem. Samozřejmě že abstraktní předek, který pouze sosá Explorer z DIContaineru smysl nemá, to ale nebylo tématem dotazu.

Snažíš se „zabít“ smysl a výhody DI

Souhlasím – uvedený příklad zabíjí DI, ale obecně neplatí, že v modelu nesmíme použít dědění, ale pouze DI. Jak DI, tak dědění jsou praxí ověřené koncepty, které oba přinášejí určité benefity a oba lze v modelových třídách kreativně využít.

Já používám abstraktní předky pro modelové třídy a hodně mě zjednodušují kód. V abstraktním předku jsem shromáždil společné funkce všech modelových tříd. Finální modelové třídy přidávají specifické funkce, kód je menší, přehlednější.

Čím je Facade zastaralé?

To jsem špatně formuloval, měl jsem na mysli pouze suffix názvu modelových tříd, nikoliv návrhový vzor Facade. Facade je dle wikipedie obecný návrhový vzor, kdy se zapouzdřuje složitější api do jednoduššího, to nezestárne nikdy. Část komunity ale používá Facade jako suffix názvu modelové třídy – proč? Máme koncepci MVC, kde část aplikace obhospodařující databázi se jmenuje Model, tak mě přijde logické použít pro třídy v této části aplikace suffix Model a kód je čitelnější.

m.brecher
Generous Backer | 765
+
0
-

@pospi

Pokud bych se pozdeji rozhodl ze tridy BasicModel, ForumModel a UserModel nebudou finalni a budu dale z nich dedit.

Dědit v modelových třídách chce určité zkušenosti, já jsem první dva roky psal všechny modelové třídy bez dědění a nekomplikoval jsem si život. Cca po 2 letech jsem přešel na určité zobecnění a modelové třídy dědím z abstraktního předka, je to ale součástí složitější koncepce, kdy toto navazuje na presenter a formuláře. Takže jak se obvykle doporučuje v modelových třídách dědění nepoužívat tak bych to Tobě také pro začátek doporučil.

Doporučuji mít všechny modelové třídy s koncovkou <*>Model a registrovat je automaticky jako služby takto:

services.neon:

search:
	model:
		in: %appDir%/Model
		classes:
			- *Model

Modelové třídy umísti do adresáře app/Model.

Finální třídy potom mohou autowirovanou databázi získat klasicky konstruktorem.

Chceš mít tři nezávislé databáze. Všeobecně se ale doporučuje používat koncepci jeden projekt – jedna databáze. Použitím několika databází v jednom projektu si zbytečně zkomplikuješ život. Doporučuji všechny tabulky umístit do jedné společné databáze – pokud není nějaký silný důvod mít databází více. Mezi databázemi nelze použít cizí klíče, bude složitější zálohování, složitější předávání závislostí.

Kamil Valenta
Člen | 762
+
0
-

m.brecher napsal(a):

Samozřejmě že abstraktní předek, který pouze sosá Explorer z DIContaineru smysl nemá, to ale nebylo tématem dotazu.

Tématem dotazu bylo jak to udělat co nejlépe. Poradit stylem, o kterém sám tvrdíš, že to smysl nemá, ale tazatel se prostě nezeptal dost dobře, je zavádějící :(

Všeobecně se ale doporučuje používat koncepci jeden projekt – jedna databáze.

Nevím kde se to všeobecně doporučuje, ale úplně nesouhlasím. Ačkoliv je to velmi častý případ, je hodně důvodů, proč to tak být nemusí. Tazatel, na rozdíl od nás, asi lépe ví, zda to tak potřebuje / musí mít.
Např. některé ERP mají API ve formě databázových procedur. V takovém případě se 2 db pracovat musíš. Mnohdy, pokud má aplikace propracovanější logy, je dobré logovat do jiné databáze, např. zmíněné zálohování se tím naopak zjednoduší.

Mezi databázemi nelze použít cizí klíče

Proč by to nešlo?

Šaman
Člen | 2635
+
+4
-

@pospi : To co hledáš je decorator
Umožní ti konfigurovat služby potomků, narozdíl od sekce services, která se snaží přímo třídu vytvořit.
Doporučuji závislost přes předka předávat spíš setterem (který se může jmenovat injectXxx) a nechat potomkům prázdný konstruktor pro jejich vlastní závislosti.

Editoval Šaman (26. 2. 20:35)