Inspiration for writing the study came from the article . An interesting article published four years ago also caught my attention â .
It was interesting that the same thoughtto implement business logic in the database".

occurred to me as well.
In addition, I wanted to keep some interesting developments that arose during the implementation for myself, especially considering that a strategic decision was recently made to change the architecture and move the business logic to the backend level. Thus, everything that was developed will soon be of no use and of no interest to anyone.
The methods described are not some kind of discovery or exceptional know how, everything is classic and has been implemented many times (for example, I applied a similar approach 20 years ago on Oracle). I just decided to compile everything in one place. Maybe it will be useful to someone. As practice has shown â quite often the same idea comes independently to different people. And itâs useful to keep it as a memory for myself.
Of course, nothing in this world is perfect; errors and typos are unfortunately possible. Criticism and comments are always welcomed and expected. And one more small detail â specific implementation details are omitted. After all, everything is still being used in a working project. So, the article serves as a study and description of the general concept, nothing more. I hope the details are sufficient for understanding the overall picture.
The general idea â âdivide and conquer, hide and possessâ
The idea is classic â a separate schema for tables, a separate schema for stored functions.
The client does not have direct access to the data. All the client can do is call a stored function and process the received response.
Roller
CREATE ROLE store;
CREATE ROLE sys_functions;
CREATE ROLE loc_audit_functions;
CREATE ROLE service_functions;
CREATE ROLE business_functions;
Schemas
Table storage schema
Target tables implementing subject entities.
CREATE SCHEMA store AUTHORIZATION store ;
System functions schema
System functions, in particular for logging table changes.
CREATE SCHEMA sys_functions AUTHORIZATION sys_functions ;
Local audit schema
Funktioner och tabeller för att implementera lokal granskning av utförandet av sparade funktioner och Àndringar av mÄl-tabeller.
SKAPA SCHEMA loc_audit_functions AUTORISATION loc_audit_functions;
Schema för servicetjÀnster
Funktioner för service- och DML-funktioner.
SKAPA SCHEMA service_functions AUTORISATION service_functions;
Schema för affÀrsfunktioner
Funktioner för slutliga affÀrsfunktioner som anropas av kunden.
SKAPA SCHEMA business_functions AUTORISATION business_functions;
à tkomstbehörigheter
Roll â DBA har full tillgĂ„ng till alla scheman (Ă„tskilt frĂ„n rollen DB Owner).
SKAPA ROLL dba_role;
BEVILJA store TILL dba_role;
BEVILJA sys_functions TILL dba_role;
BEVILJA loc_audit_functions TILL dba_role;
BEVILJA service_functions TILL dba_role;
BEVILJA business_functions TILL dba_role;
Roll â USER har privilegiet KĂR i schemat business_functions.
SKAPA ROLL user_role;
Privilegier mellan scheman
GRANT
Eftersom alla funktioner skapas med attributet SECURITY DEFINER krĂ€vs instruktionen Ă
TERKALLA KĂR PĂ
ALLA FUNKTIONER⊠FRĂ
N public;
Ă
TERKALLA KĂR PĂ
ALLA FUNKTIONER I SCHEMA sys_functions FRĂ
N public ;
Ă
TERKALLA KĂR PĂ
ALLA FUNKTIONER I SCHEMA loc_audit_functions FRĂ
N public ;
Ă
TERKALLA KĂR PĂ
ALLA FUNKTIONER I SCHEMA service_functions FRĂ
N public ;
Ă
TERKALLA KĂR PĂ
ALLA FUNKTIONER I SCHEMA business_functions FRĂ
N public ;
BEVILJA ANVĂNDNING PĂ
SCHEMA sys_functions TILL dba_role ;
BEVILJA KĂR PĂ
ALLA FUNKTIONER I SCHEMA sys_functions TILL dba_role ;
BEVILJA ANVĂNDNING PĂ
SCHEMA loc_audit_functions TILL dba_role ;
BEVILJA KĂR PĂ
ALLA FUNKTIONER I SCHEMA loc_audit_functions TILL dba_role ;
BEVILJA ANVĂNDNING PĂ
SCHEMA service_functions TILL dba_role ;
BEVILJA KĂR PĂ
ALLA FUNKTIONER I SCHEMA service_functions TILL dba_role ;
BEVILJA ANVĂNDNING PĂ
SCHEMA business_functions TILL dba_role ;
BEVILJA KĂR PĂ
ALLA FUNKTIONER I SCHEMA business_functions TILL dba_role ;
BEVILJA KĂR PĂ
ALLA FUNKTIONER I SCHEMA business_functions TILL user_role ;
BEVILJA ALLA PRIVILEGIER PĂ
SCHEMA store TILL GRUPP business_functions ;
BEVILJA ALLA PRIVILEGIER PĂ
ALLA TABELLER I SCHEMA store TILL business_functions ;
BEVILJA ANVĂNDNING PĂ
ALLA SEKVENSER I SCHEMA store TILL business_functions ;
SÄ, databasschemat Àr klart. Du kan börja fylla det med data.
MÄl-tabeller
Skapandet av tabeller Àr trivialt. Inga sÀrdrag, förutom att det beslutades att avstÄ frÄn att anvÀnda SERIAL och generera sekvenser explicit. Dessutom, sjÀlvklart, maximalt utnyttjande av instruktionen
COMMENT ON ...Kommentarer för alla objekt, utan undantag.
Lokal granskning
För att logga körningen av sparade funktioner och Àndringar av mÄl-tabeller anvÀnds en lokal gransknings-tabell som inkluderar detaljer om kundanslutningen, mÀrke för den anropade modulen, faktiska vÀrden för in- och utgÄngsparametrar i form av JSON.
Systemfunktioner
Avsedda för loggning av Àndringar i mÄl-tabeller. Utgör triggerfunktioner.
Mall â systemfunktion
---------------------------------------------------------
-- INFĂR
CREATE OR REPLACE FUNCTION sys_functions.table_insert_log ()
RETURNS TRIGGER AS $$
BEGIN
PERFORM loc_audit_functions.make_log( ' '||'table' , 'insert' , json_build_object('id', NEW.id) );
RETURN NULL ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER table_after_insert AFTER INSERT ON storage.table FOR EACH ROW EXECUTE PROCEDURE sys_functions.table_insert_log();
---------------------------------------------------------
-- UPPDATERA
CREATE OR REPLACE FUNCTION sys_functions.table_update_log ()
RETURNS TRIGGER AS $$
BEGIN
IF OLD.column != NEW.column
THEN
PERFORM loc_audit_functions.make_log( ' '||'table' , 'update' , json_build_object('OLD.column', OLD.column , 'NEW.column', NEW.column ) );
END IF ;
RETURN NULL ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER table_after_update AFTER UPDATE ON storage.table FOR EACH ROW EXECUTE PROCEDURE sys_functions.table_update_log();
---------------------------------------------------------
-- TA BORT
CREATE OR REPLACE FUNCTION sys_functions.table_delete_log ()
RETURNS TRIGGER AS $$
BEGIN
PERFORM loc_audit_functions.make_log( ' '||'table' , 'delete' , json_build_object('id', OLD.id ) );
RETURN NULL ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER table_after_delete AFTER DELETE ON storage.table FOR EACH ROW EXECUTE PROCEDURE sys_functions.table_delete_log ();TjÀnstefunktioner
Ăr avsedda för att genomföra tjĂ€nstrelaterade och DML-operationer pĂ„ mĂ„ltabellerna.
Mall â tjĂ€nstefunktion
--INFĂR
--RETURERA id FĂR NY RAD
CREATE OR REPLACE FUNCTION service_functions.table_insert ( new_column store.table.column%TYPE )
RETURNS integer AS $$
DECLARE
new_id integer ;
BEGIN
-- Generera nytt id
new_id = nextval('store.table.seq');
-- Införa i tabellen
INSERT INTO store.table
(
id ,
column
)
VALUES
(
new_id ,
new_column
);
RETURN new_id ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
--TA BORT
--RETURERA RADNUMMER SOM HAR TAGITS BORT
CREATE OR REPLACE FUNCTION service_functions.table_delete ( current_id integer )
RETURNS integer AS $$
DECLARE
rows_count integer ;
BEGIN
DELETE FROM store.table WHERE id = current_id;
GET DIAGNOSTICS rows_count = ROW_COUNT;
RETURN rows_count ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- UPPDATERA DETALJER
-- RETURERA RADNUMMER SOM HAR UPPDATERATS
CREATE OR REPLACE FUNCTION service_functions.table_update_column
(
current_id integer
,new_column store.table.column%TYPE
)
RETURNS integer AS $$
DECLARE
rows_count integer ;
BEGIN
UPDATE store.table
SET
column = new_column
WHERE id = current_id;
GET DIAGNOSTICS rows_count = ROW_COUNT;
RETURN rows_count ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;AffÀrsfunktioner
Ăr avsedda för slutliga affĂ€rsfunktioner som anropas av klienten. Ă tervĂ€nder alltid â JSON. För att fĂ„nga och logga körningsfel anvĂ€nds ett block EXCEPTION.
Mall â affĂ€rsfunktion
SKAPA ELLER ERSĂTT FUNKTION business_functions.business_function_template(
--IngÄngsparameter
)
RETURNERAR JSON AS $$
DEKLARERA
------------------------
--för undantagshantering
error_message text ;
error_json json ;
result json ;
------------------------
BĂRJA
--LOGGNING
UTFĂR loc_audit_functions.make_log
(
'business_function_template',
'STARTAD',
json_build_object
(
--IN Parametrar
)
);
UTFĂR business_functions.notice('business_function_template');
--STARTA AFFĂRSDELEN
--AVSLUTA AFFĂRSDELEN
-- FRAMGĂ
NGSRESULTAT
UTFĂR business_functions.notice('result');
UTFĂR business_functions.notice(result);
UTFĂR loc_audit_functions.make_log
(
'business_function_template',
'AVSLUTAD',
json_build_object( 'result',result )
);
RETURNERA result ;
----------------------------------------------------------------------------------------------------------
-- UNDANTAGSHANTERING
UNDANTAG
NĂR ANDRA DĂ
UTFĂR loc_audit_functions.make_log
(
'business_function_template',
'STARTAD',
json_build_object
(
--IN Parametrar
) , TRUE );
UTFĂR loc_audit_functions.make_log
(
'business_function_template',
' FEL',
json_build_object('SQLSTATE',SQLSTATE ), TRUE
);
UTFĂR loc_audit_functions.make_log
(
'business_function_template',
' FEL',
json_build_object('SQLERRM',SQLERRM ), TRUE
);
HĂMTA STACKAD DIAGNOSTIK error_message = RETURNED_SQLSTATE ;
UTFĂR loc_audit_functions.make_log
(
'business_function_template',
' FEL-RETURNED_SQLSTATE',json_build_object('RETURNED_SQLSTATE',error_message ), TRUE );
HĂMTA STACKAD DIAGNOSTIK error_message = COLUMN_NAME ;
UTFĂR loc_audit_functions.make_log
(
'business_function_template',
' FEL-KOLUMN_NAMN',
json_build_object('COLUMN_NAME',error_message ), TRUE );
HĂMTA STACKAD DIAGNOSTIK error_message = CONSTRAINT_NAME ;
UTFĂR loc_audit_functions.make_log
(
'business_function_template',
' FEL-KONSTRIKT_NAMN',
json_build_object('CONSTRAINT_NAME',error_message ), TRUE );
HĂMTA STACKAD DIAGNOSTIK error_message = PG_DATATYPE_NAME ;
UTFĂR loc_audit_functions.make_log
(
'business_function_template',
' FEL-PG_DATATYPE_NAME',
json_build_object('PG_DATATYPE_NAME',error_message ), TRUE );
HĂMTA STACKAD DIAGNOSTIK error_message = MESSAGE_TEXT ;
UTFĂR loc_audit_functions.make_log
(
'business_function_template',
' FEL-MEDDELANDE_TEXT',json_build_object('MESSAGE_TEXT',error_message ), TRUE );
HĂMTA STACKAD DIAGNOSTIK error_message = SCHEMA_NAME ;
UTFĂR loc_audit_functions.make_log
(
'business_function_template',
' FEL-SCHEMA_NAMN',json_build_object('SCHEMA_NAME',error_message ), TRUE );
HĂMTA STACKAD DIAGNOSTIK error_message = PG_EXCEPTION_DETAIL ;
UTFĂR loc_audit_functions.make_log
(
'business_function_template',
' FEL-PG_EXCEPTION_DETAIL',
json_build_object('PG_EXCEPTION_DETAIL',error_message ), TRUE );
HĂMTA STACKAD DIAGNOSTIK error_message = PG_EXCEPTION_HINT ;
UTFĂR loc_audit_functions.make_log
(
'business_function_template',
' FEL-PG_EXCEPTION_HINT',json_build_object('PG_EXCEPTION_HINT',error_message ), TRUE );
HĂMTA STACKAD DIAGNOSTIK error_message = PG_EXCEPTION_CONTEXT ;
UTFĂR loc_audit_functions.make_log
(
'business_function_template',
' FEL-PG_EXCEPTION_CONTEXT',json_build_object('PG_EXCEPTION_CONTEXT',error_message ), TRUE );
HĂJ VARNING 'ALARM: %' , SQLERRM ;
VĂLJ json_build_object
(
'isError' , TRUE ,
'errorMsg' , SQLERRM
) INTO error_json ;
RETURNERA error_json ;
SLUT
$$ SPRĂ
K plpgsql SĂKERHET DEFINERARE;Resultat
För att beskriva den övergripande bilden, tycker jag att det Àr tillrÀckligt. Om nÄgon Àr intresserad av detaljer och resultat, skriv gÀrna i kommentarerna, sÄ kompletterar jag gÀrna bilden med ytterligare nyanser.
P.S.
Loggning av en enkel felaktighet - typ av ingÄende parameter
-[ POST 1 ]-
date_trunc | 2020-08-19 13:15:46
id | 1072
usename | emp1
log_module | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status | STARTED
jsonb_pretty | {
| "dko": {
| "id": 4,
| "type": "Type1",
| "title": "SKAPAD AV addKD",
| "Weight": 10,
| "Tr": "300",
| "reduction": 10,
| "isTrud": "TRUE",
| "description": "beskrivning",
| "lowerTr": "100",
| "measurement": "mÀtning1",
| "methodology": "m1",
| "passportUrl": "filer",
| "upperTr": "200",
| "weightingFactor": 100.123,
| "actualTrValue": null,
| "upperTrCalcNumber": "120"
| },
| "CardId": 3
| }
-[ POST 2 ]-
date_trunc | 2020-08-19 13:15:46
id | 1073
usename | emp1
log_module | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status | FEL
jsonb_pretty | {
| "SQLSTATE": "22P02"
| }
-[ POST 3 ]-
date_trunc | 2020-08-19 13:15:46
id | 1074
usename | emp1
log_module | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status | FEL
jsonb_pretty | {
| "SQLERRM": "ogiltig inmatningssyntax för typ numerisk: "null""
| }
-[ POST 4 ]-
date_trunc | 2020-08-19 13:15:46
id | 1075
usename | emp1
log_module | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status | FEL-RETURNED_SQLSTATE
jsonb_pretty | {
| "RETURNED_SQLSTATE": "22P02"
| }
-[ POST 5 ]-
date_trunc | 2020-08-19 13:15:46
id | 1076
usename | emp1
log_module | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status | FEL-KOLUMN_NAMN
jsonb_pretty | {
| "COLUMN_NAME": ""
| }
-[ POST 6 ]-
date_trunc | 2020-08-19 13:15:46
id | 1077
usename | emp1
log_module | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status | FEL-BEGRĂNSNING_NAMN
jsonb_pretty | {
| "CONSTRAINT_NAME": ""
| }
-[ POST 7 ]-
date_trunc | 2020-08-19 13:15:46
id | 1078
usename | emp1
log_module | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status | FEL-PG_DATATYPE_NAMN
jsonb_pretty | {
| "PG_DATATYPE_NAME": ""
| }
-[ POST 8 ]-
date_trunc | 2020-08-19 13:15:46
id | 1079
usename | emp1
log_module | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status | FEL-MEDDDELANDE_TEXT
jsonb_pretty | {
| "MESSAGE_TEXT": "ogiltig inmatningssyntax för typ numerisk: "null""
| }
-[ POST 9 ]-
date_trunc | 2020-08-19 13:15:46
id | 1080
usename | emp1
log_module | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status | FEL-SCHEMA_NAMN
jsonb_pretty | {
| "SCHEMA_NAME": ""
| }
-[ POST 10 ]-
date_trunc | 2020-08-19 13:15:46
id | 1081
usename | emp1
log_module | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status | FEL-PG_UNDANTAG_DETALJ
jsonb_pretty | {
| "PG_EXCEPTION_DETAIL": ""
| }
-[ POST 11 ]-
date_trunc | 2020-08-19 13:15:46
id | 1082
usename | emp1
log_module | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status | FEL-PG_UNDANTAG_HINT
jsonb_pretty | {
| "PG_EXCEPTION_HINT": ""
| }
-[ POST 12 ]-
date_trunc | 2020-08-19 13:15:46
id | 1083
usename | emp1
log_module | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status | FEL-PG_UNDANTAG_KONTEXT
jsonb_pretty | {
usename | emp1
log_module | addKD
log_module_hash | 0b4c1529a89af3ddf6af3821dc790e8a
status | FEL-MEDDDELANDE_TEXT
jsonb_pretty | {
| "MESSAGE_TEXT": "ogiltig inmatningssyntax för typ numerisk: "null""
| }KĂ€lla: habr.com
